1

As I got a perfect answer for the question: Specializing class with SFINAE

For completeness I insert the correct solution as example here again:

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

template < typename T, typename UNUSED = void> class X;

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

template < typename T > 
class X<T, typename std::enable_if< !std::is_same< int, typename T::TRAIT>::value, void >::type>
{   
    public:
        X() { std::cout << "Second" << std::endl; }
};

int main()
{
     X<AA> a;
     X<BB> b;
}

But if I have to use a parameter pack for further use, I see no chance to write the things down like:

template < typename T, typename ...S, typename UNUSED = void> class X;

error: parameter pack 'S' must be at the end of the template parameter list

Having the definition in a different order like

template < typename T, typename UNUSED = void, typename ...S> class X;

ends up in problems if the first additional type is in use.

OK, what I describe is a technical solution which I can't find actually. Maybe there is a different one. What is my underlying problem: I need 2 different constructors for the class which call different base class constructors. But because both constructors have the same set of parameters I see no chance to specialize the constructors itself.

If specialize constructors can work, it can be something like that:

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( const V* =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( const V* =nullptr) { std::cout << "Second" << std::endl; }

};

error: 'template template Y::Y(const V*)' cannot be overloaded

But as already mentioned... I have no idea if that can be done.

To show the underlying problem, I would give the following example which shows the different use of base class constructors dependent on a trait which is defined in the base class.

template <typename T, typename ... S>: public T
class Z
{
    public:
        // should work if T defines a trait
        Z( typename T::SomeType t): T( t ) {}
        // should be used if T defines another trait
        Z( typename T::SomeType t): T( )   {}
};
Community
  • 1
  • 1
Klaus
  • 24,205
  • 7
  • 58
  • 113
  • 2
    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, but the last code snippet says *"should be used if T did not define this trait"*, you mean if `::TRAIT` does not exist ? – Piotr Skotnicki May 17 '16 at 16:45
  • @Holt No, the trait is a own template class which uses also a parameter pack. With sfinae I have to check if the parameter pack of that type is empty. I avoid this special things to make it not more complex here. – Klaus May 17 '16 at 16:48
  • Could you show us a full code with a non-working overloaded constructor because as @PiotrSkotnicki mentioned your question seems to contain two different problems (checking if `TRAIT` exists and checking if `TRAIT` is a specific type). – Holt May 17 '16 at 16:49
  • @Holt: Sorry, I corrected this in the question. I need to check if my trait fulfills some condition. The trait is always present. – Klaus May 17 '16 at 16:53
  • @PiotrSkotnicki OK, I test that idea. I am wondering that sfinae takes place after the check of overloading rules, which results in two times the same function also if only one SFINAE expression works. – Klaus May 17 '16 at 16:58
  • @PiotrSkotnicki I end up in "error: no type named 'type' in 'struct std::enable_if'" which is terrible for me. I thought that is the idea of sfinae to break the instantiation. I will come back tomorrow. Because old questions will not be answered after correction I will remove this one and copy it to a new one... – Klaus May 17 '16 at 17:11
  • @Klaus this error should not appear unless you hard-coded enable_if's condition – Piotr Skotnicki May 17 '16 at 17:18

2 Answers2

4

Instead of

template < typename T, typename ...S, typename UNUSED = void> class X;

you may add a layer:

template <typename T, typename Dummy = void, typename ... Ts> class X_impl {};

and then

template <typename T, typename ...Ts>
using X = X_impl<T, void, Ts...>;

For SFINAE, as default template parameter is not part of signature,

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

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

Should be rewritten, as for example:

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

template <typename U = T,
          std::enable_if_t<!std::is_same<int, typename U::TRAIT>::value>* = nullptr>
Y() { std::cout << "Second" << std::endl; }

In C++20, requires might simplify code a lot:

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

    Y() requires(!std::is_same< int, typename U::TRAIT>::value)
    { std::cout << "Second" << std::endl; }
// ...
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0
template<class...>struct types_tag{using type=types_tag;};
template<class...Ts>constexpr types_tag<Ts...> types{};

These helpers let you pass many types around as a bundle, a single argument.

Now your type X can look like:

template<class T, class types, class=void>
class X;
template<class T, class...Ts>>
class X<T, types_tag<Ts...>, std::enable_if_t<true>> {
};

Users of X pass in X<T, types_tag<int, double, char>.

You can write adapters like:

template<class T, class...Ts>
using X_t = X<T, types_tag<Ts...>>;

where we make the using alias have a better name than the implementation struct.

Bundles of types passed around as one type can make a whole bunch of metaprogramming simple. You can pass more than one bundle; and you can pass a types_tag by-value to a function as an argument to enable easy deduction of the contents of the bundle.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524