Reputation: 25613
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( ) {}
};
Upvotes: 1
Views: 1385
Reputation: 217293
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; }
// ...
};
Upvotes: 4
Reputation: 275415
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.
Upvotes: 0