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

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • what form of your call to `foo` does not work? – Piotr Skotnicki Oct 01 '14 at 10:22
  • @PiotrS. : The second form doesn't work. You can't call += (a non-const method) on the rvalue `std::declval()`. But you can call + on rvalues. Compare 2+2 and 2+=2 – MSalters Oct 01 '14 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. – dyp Oct 01 '14 at 10:52
  • @dyp: I expect to use this method with short, float, double and `std::complex<>`. – MSalters Oct 01 '14 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. – Jason R Oct 01 '14 at 13:55

4 Answers4

16

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    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. – MSalters Oct 01 '14 at 10:59
  • 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 Oct 01 '14 at 12:12
  • @CashCow ok, I read the Q again, and I understood. The compilation should fail when `operator+=` is missing – BЈовић Oct 01 '14 at 12:37
  • @CashCow: No. The compilation wont *fail* if `+=` doesn't exist for `a`. It would result in SFINAE, i.e the compiler would ignore this function, and try something else if available. Only if there is no matching function, the compilation would fail. This function has no role in compilation failure. – Nawaz Oct 01 '14 at 13:33
  • Yes, subsitution failure itself is not an error (Thus SFINAE) but you'll get "no matching function for.." unless of course there is an (alternative) matching function. – CashCow Oct 01 '14 at 13:47
  • 1
    @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). – MSalters Oct 01 '14 at 14:02
  • @CashCow: Your comments are confusing, especially this line *"It should fail to compile"* which is untrue. – Nawaz Oct 01 '14 at 14:41
  • My comment is confusing becauwse it was a reply to someone who deleted their comment and I should have put their name in. – CashCow Oct 01 '14 at 15:24
  • 1
    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) – Qqwy Feb 04 '17 at 09:43
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, ...)
{
}
gexicide
  • 38,535
  • 21
  • 92
  • 152
  • 4
    The second `declval` might also have to use `T&`, depending on the details of the function (`a += b` or `a += move(b)`). – dyp Oct 01 '14 at 10:50
  • I knew `std::declval()` 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 Oct 01 '14 at 10:51
  • 3
    @Nawaz: Right, but it is closest to OPs approach. – gexicide Oct 01 '14 at 10:52
  • After finishing my overload set, I came back to this solution. The overload set contained another `template 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 Oct 03 '14 at 08:34
  • @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? – Nawaz Oct 03 '14 at 11:37
  • @Nawaz: that might be a challenge, as `foo` is obviously a simplification. Summarized, there's a pair of overloads `template auto foo() -> decltype(a+=b)` and `template auto foo() -> decltype(bar(a,b))` such that only one of the two can be instantiated at any time. That's why I needed to eliminate the first version if I need the second. What happens though is that SFINAE happens only in the instantiation phase, but the second definition is already rejected by GCC at compile time. – MSalters Oct 03 '14 at 16:53
  • @MSalters: I believe it has nothing to do [with my solution](http://coliru.stacked-crooked.com/a/3cc5ff81d8b63b09) and instead you're doing something incorrectly. I know what you described is oversimplification which doesn't show the issue but I still believe you're doing something incorrectly. – Nawaz Oct 04 '14 at 04:32
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.

CashCow
  • 30,981
  • 5
  • 61
  • 92
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;
}
ikh
  • 10,119
  • 1
  • 31
  • 70