1

I have no idea which rules must be considered to use SFINAE to method overloading. I run multiple times in problems because there seams to be more rules involved as I know. So I hope that there is a set of rules which can be explained in short to help to solve the problems in general instead of asking questions again and again.

My start point was here: Specializing class with SFINAE if a parameter pack is needed

Code 1

class AA { public: using TRAIT = int; };
class BB { public: using TRAIT = float; };

template < typename T>
class Y
{   
    public:
        template <typename U = T, typename V= typename std::enable_if< std::is_same< int, typename U::TRAIT>::value, int >::type>
            Y( ) { std::cout << "First" << std::endl; }

        template <typename U = T, typename V= typename std::enable_if< !std::is_same< int, typename U::TRAIT>::value, float >::type>
            Y( ) { std::cout << "Second" << std::endl; }

};

error: 'template template Y::Y()' cannot be overloaded

To this problem I got a comment:

"the "constructor cannot be overloaded" problem can be solved by adding a dummy and defaulted template parameter (like , typename Z = void) to one of the constructors"

OK, changing my code to:

Code 2

template < typename T>
class Y
{
    public:
        template <typename U = T, typename V= typename std::enable_if< std::is_same< int, typename U::TRAIT>::value, int >::type, typename X=int>
            Y( X* = nullptr) { std::cout << "First" << std::endl; }

        template <typename U = T, typename V= typename std::enable_if< !std::is_same< int, typename U::TRAIT>::value, float >::type>
            Y( ) { std::cout << "Second" << std::endl; }
};

OK, it compiles. But simplifying seams to works also which makes me doubtful.

Code 3

template < typename T>
class Y
{   
    public:
        template <typename U = T, typename V= typename std::enable_if< std::is_same< int, typename U::TRAIT>::value, int >::type>
            Y( int* = nullptr) { std::cout << "First" << std::endl; }

        template <typename U = T, typename V= typename std::enable_if< !std::is_same< int, typename U::TRAIT>::value, float >::type>
            Y( ) { std::cout << "Second" << std::endl; }
};

The last example works also! It has a default parameter in ONE of the constructors, but this parameter is not a templated one.

For me it is now quite unclear which rules are working behind to get the things to run here.

My first misunderstanding is, that I thought that SFINAE takes place by instantiating the constructor template and only one constructor becomes available. This is not the truth! Each parameter set for all constructors must be different!? Why? And in addition, why it should or must be a templated parameter? My example 3 seams to work but others gave me the advice to use a dummy and defaulted template parameter (like , typename Z = void). Can someone give me a bit background information on this topic?

Community
  • 1
  • 1
Klaus
  • 24,205
  • 7
  • 58
  • 113
  • http://stackoverflow.com/a/36500292/3953764 – Piotr Skotnicki May 18 '16 at 07:26
  • *"I thought that SFINAE takes place by instantiating the constructor template and only one constructor becomes available. This is not the truth!"* => But you get the error even without a definition of AA or BB. So the issue is not related to template instantiation. Restating what's already been said: it's just as how `void foo(bool b = false)` and `void foo(bool b = true)` cannot coexist in a definition merely because they have different defaults. So it is with `` and ``. – HostileFork says dont trust SE May 18 '16 at 09:13

1 Answers1

6

signature of the function should be different, and default value (for template as for regular argument) is not part of signature. So

template <typename U = T, typename V= typename std::enable_if< std::is_same< int, typename U::TRAIT>::value, int >::type>
Y();

template <typename U = T, typename V= typename std::enable_if< !std::is_same< int, typename U::TRAIT>::value, float >::type>
Y();

Are simply

template <typename U, typename V>
Y();

template <typename U, typename V>
Y();

So, a redifinition.

Whereas

template <typename U = T, typename V = typename std::enable_if< std::is_same< int, typename U::TRAIT>::value, int >::type, typename X=int>
Y( X* = nullptr);

template <typename U = T, typename V= typename std::enable_if< !std::is_same< int, typename U::TRAIT>::value, float >::type>
Y( );

are

template <typename U, typename V, typename X>
Y(X*);

template <typename U, typename V>
Y();

and so different signatures.

A way to avoid to always add parameter is to do something like

template <typename U = T, typename std::enable_if<std::is_same<int, typename U::TRAIT>::value>::type* = nullptr>
Y();

template <typename U = T, typename std::enable_if<!std::is_same<int, typename U::TRAIT>::value>::type* = nullptr>
Y();

which result in different signature:

template <typename U, typename std::enable_if<std::is_same<int, typename U::TRAIT>::value>::type*>
Y();

template <typename U, typename std::enable_if<!std::is_same<int, typename U::TRAIT>::value>::type*>
Y();
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • The last example to "simply" use the default initialized type pointer is strange to me. I thought that this ends up in `template ` which is also the same for both definitions. Can you add a short explanation why here the signature expands with the complete expression of the SFINAE construct? – Klaus May 18 '16 at 08:20
  • @Klauss, Even if `std::enable_if_t<..>*` result in `void*` for both, it is different signature (before). But if they both result in `void*` for some `T`, you will have ambiguous call anyway. so the condition should be chosen carefully (some write `disable_if` helper to write same condition in both). – Jarod42 May 18 '16 at 08:45
  • @Klauss In seeing this stuff for the first time, I thought it was useful to throw in a `static_assert(std::is_same::value, "cannot explicitly parameterize template");` Just for the sake of realizing that there is a degree of freedom here in the invocation you don't want people to use, to override the default of U = T that was thrown in just for SFINAE. *(Though for this specifically, you [can't explicitly parameterize templated constructors](http://stackoverflow.com/a/2786963/211160).)* So, e.g. `Y yaa; yaa.foo();` – HostileFork says dont trust SE May 18 '16 at 09:32
  • @HostileFork: The `static_assert` is not needed for constructor, as user cannot specify template parameter for constructor. – Jarod42 May 18 '16 at 10:21