14

I'm trying to overload operator T() using SFINAE to return a copy when T is a fundamental type, and a const reference when T is a class.

When using a double in my example below, I can't get the 2nd overload (with std::is_class) to be removed.

That is, the error I'm getting is:

error: no type named ‘type’ in ‘struct std::enable_if<false, const double&>’
operator typename std::enable_if< std::is_class<T>::value, const T&>::type () const
^

What am I doing wrong?

#include <iostream>
#include <type_traits>

template<typename T>
struct Foo
{
    operator typename std::enable_if<!std::is_class<T>::value, T >::type () const
    {
        return _val;
    }

    operator typename std::enable_if< std::is_class<T>::value, const T&>::type () const
    {
        return _val;
    }

    T _val;
};

int main()
{
    Foo<double> f1;
    f1._val = 0.3;

    double d = f1;
    std::cout << d << std::endl;
    return 0;
}
Praetorian
  • 106,671
  • 19
  • 240
  • 328
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213

2 Answers2

16

T is already known at the time your class member functions are instantiated, so no substitution occurs, and instead of SFINAE, you get a hard error. The easiest workaround is to introduce a dummy template parameter for those operator overloads and default it to T so that type deduction can still occur.

template<typename U = T>
operator typename std::enable_if<!std::is_class<U>::value, U >::type () const
{
    return _val;
}

template<typename U = T>
operator typename std::enable_if< std::is_class<U>::value, const U&>::type () const
{
    return _val;
}

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • For a slightly different example and additional details, see http://stackoverflow.com/questions/18100297/how-can-i-use-stdenable-if-in-a-conversion-operator – Asher Nov 04 '15 at 07:11
  • @Asher Using SFINAE in the default template argument works in the question you've linked to, but in the question above, where the OP is trying to use it to define two mutually exclusive overloads, it won't [as explained here](http://stackoverflow.com/a/29502338/241631). – Praetorian Nov 04 '15 at 16:37
  • Might this not allow the compiler to instantiate unwanted type conversion operators too? – Museful May 06 '18 at 14:54
  • @Museful What do you mean by unwanted? Can you give an example? – Praetorian May 06 '18 at 17:06
  • Perhaps I should rather ask this: would it be exactly equivalent to use `T` instead of `U` as `enable_if`'s 2nd parameter? (and `const T&` instead of `const U&`) – Museful May 06 '18 at 17:58
  • @Museful Yes, that would work, but again, what are you trying to prevent? It works because `T=U`. – Praetorian May 06 '18 at 22:09
  • @Praetorian So is `T=U` guaranteed to always be true? Nothing (such as `int i = Foo()` or anything else) will cause the compiler to come here with `T`!=`U`? – Museful May 07 '18 at 00:33
5

Whilst not solving the problem of why the incorrect operator was not discarded, to solve the particular issue at hand, that is, to return by const ref for class types or by value for others, a solution can be found using std::conditional.

template< bool B, class T, class F >
struct conditional;

Provides member typedef type, which is defined as T if B is true at compile time, or as F if B is false.

Working example:

#include <iostream>
#include <type_traits>

template<typename T>
struct Foo
{
    operator typename std::conditional<
        std::is_class<T>::value, const T&, T>::type () const
    {
        return _val;
    }

    T _val;
};

int main()
{
    Foo<double> f1;
    f1._val = 0.3;

    double d = f1;
    std::cout << d << std::endl;
    return 0;
}
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • 1
    This seems like a decent way to avoid needing an answer to your question, but it's worth noting that it cannot be generalised to work when the bodies of your operator are not 100% identical. –  Dec 11 '14 at 22:11
  • @hvd I agree. However, in this particular instance, where all I want to do is return by const ref for class types, and by value for other types, I'm thinking using `std::conditional` is a simpler/shorter approach than having 2 `std_enable_if` overloads. Would you agree? – Steve Lorimer Dec 11 '14 at 22:15
  • 1
    Yeah, if the bodies of the two operators are identical, I'd definitely go with the `std::conditional` solution. – Praetorian Dec 11 '14 at 22:19
  • @SteveLorimer Sure, I agree it's an improvement too. –  Dec 11 '14 at 22:34
  • For some reason, I've been having trouble with the std::enable_if overloads version when trying to use arithmetic via implicit conversions (i.e. `3 + Foo / 2`). The std::conditional version works perfectly, though, and the new `if constexpr` construct should allow for non-identical operator bodies now. – Joshua Little Feb 25 '20 at 22:21