14

I'm trying to eliminate an overload from an overload set if operator+= is missing.

I know how to check if T+T is legal :

template<typename T,
         typename CheckTplusT = decltype(std::declval<T>() + std::declval<T>())>
void foo(T a, T b, ...)
{
  a = a + b;
}

but this doesn't work for +=

template<typename T,
         typename CheckTplusT = decltype(std::declval<T>() += std::declval<T>())>
void foo(T a, T b, ...)
{
  a += b;
}

Is this fixable by using another expression inside decltype or do I need another SFINAE construct?

The reason I need this eliminated from the overload set is that it clashes with another overload that accepts a functor to be used as an alternative to +=. Compilers are VS2013, gcc4.8

5
  • what form of your call to foo does not work? Commented Oct 1, 2014 at 10:22
  • @PiotrS. : The second form doesn't work. You can't call += (a non-const method) on the rvalue std::declval<T>(). But you can call + on rvalues. Compare 2+2 and 2+=2 Commented Oct 1, 2014 at 10:38
  • 1
    @MSalters If += is a method, you can call it on rvalues (unless the method has a & qualifier). The problem is with built-in += and fundamental types. Commented Oct 1, 2014 at 10:52
  • @dyp: I expect to use this method with short, float, double and std::complex<>. Commented Oct 1, 2014 at 10:54
  • 1
    While this question has already been answered well, you could also use enable_if if you have Boost available. Boost.TypeTraits' boost::has_plus_assign could work. This might be the preferred non-C++11 option. Commented Oct 1, 2014 at 13:55

4 Answers 4

17

I would write the second form as:

template<typename T>
auto foo(T a, T b, ...) -> decltype( a+=b, void() )
{
  a += b;
}

The deduced type for decltype(a+=b, void()) would be just void if the expression a+=b is valid, else it would result in SFINAE.

Well, even in the first form, I would use the trailing-return type approach.

Sign up to request clarification or add additional context in comments.

11 Comments

Accepted. The rest of the overload set already needed trailing return types for other reasons (return type depending on functor argument), so this solution actually increases the similarity between the different overloads. @gexicide's solution was closer to my original implementation, but that's no benefit to future maintainers.
It should fail to compile. It's invalid as there is no += for A. (Even if you don't use temporaries in your call to Foo)
@CashCow ok, I read the Q again, and I understood. The compilation should fail when operator+= is missing
@CashCow: Which is entirely acceptable. If you try foo(nullptr, nullptr) it's appropriate to complain that foo can't take two null pointers as arguments (logically incorrect).
Let me just say that this solution is extremely beautiful, and the only way I found (so far) that works as a generic member detector. (other solutions inevitably needing a separate TMP struct per check)
|
12

You need an lvalue on the left hand side of += but your solution has an xvalue. As dyp has stated in the comments, you can use declval<T&> to get an lvalue. This works fine (just tested it):

template<typename T,
         typename CheckTplusT = decltype(std::declval<T&>() += std::declval<T>())>
void foo(T a, T b, ...)
{
}

7 Comments

The second declval might also have to use T&, depending on the details of the function (a += b or a += move(b)).
I knew std::declval<T&>() would work but I still dislike the solution. It looks so ugly. Also, if the operator+= takes non-const reference argument, then this solution wouldn't work. The trailing-return-type solution is far cleaner, better and correct, as the programmer doesn't have to deal with the value-category of the objects. Let the compilers do it.
@Nawaz: Right, but it is closest to OPs approach.
After finishing my overload set, I came back to this solution. The overload set contained another template<typename T> foo and the compiler rightfully complained about a redeclaration with a different return type when I used @Nawaz solution. That's a phase 1 problem. But this solution has different template arguments, which is fine.
@MSalters: Could you please post the code which gave redeclaration issue when using my solution, but the approach shown by this answer doesn't give any such issue?
|
1

How about this? it's the method used before std::declval.

template<typename T,
         typename CheckTplusT = decltype(*(T*)nullptr += std::declval<T>())>
void foo(T a, T b, ...)
{
  a += b;
  std::cout << "foo with +=" << std::endl;
}

1 Comment

std::declval<T&>() is cleaner.
1

Adding this main() function:

int main()
{
    int x = 1, y = 2;
    foo( x, y );
}

This is what the compiler error is:

 main.cpp: In function int main():  main.cpp:15:15: error: no matching
 function for call to foo(int&, int&)
      foo( x, y );
            ^  main.cpp:15:15: note: candidate is:  
 main.cpp:7:6: note: template<class T, class CheckTplusT> void foo(T, T, ...)  void

 foo(T a, T b, ...)
   ^ main.cpp:7:6: note:   template argument deduction/substitution failed: 
    main.cpp:6:60: error:    
      using xvalue (rvalue reference) as lvalue
       typename CheckTplusT = decltype(std::declval<T>() += std::declval<T>())>

The key line is using xvalue (rvalue reference) as lvalue

This is the documentation for declval

This workaround works for me:

template<typename T,
     typename CheckTpluseqT = decltype(*std::declval<T*>() += *std::declval<T*>())>
void foo(T &a, T b, ...)
{
   a += b;
 }

int main()
{
   int a = 1, b = 2;
   foo( a, b );
   std::cout << a << std::endl;
}

outputs 3

You can also use declval<T&> of course.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.