如果需要参数包,则使用SFINAE专门化类

Specializing class with SFINAE if a parameter pack is needed

本文关键字:SFINAE 专门化 参数 如果      更新时间:2023-10-16

因为我得到了这个问题的完美答案:SFINAE 专业课程

为了完整起见,我再次插入正确的解决方案作为示例:

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;
}

但是,如果我必须使用一个参数包来进一步使用,我看不出有机会写下这样的东西:

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

错误:参数包"S"必须位于模板参数列表的末尾

以不同的顺序定义,如

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

如果使用第一个附加类型,则最终会出现问题。

好吧,我所描述的是一个我实际上找不到的技术解决方案。也许还有一个不同的。我的根本问题是:我需要两个不同的构造函数来调用不同的基类构造函数。但由于两个构造函数都有相同的参数集,我认为没有机会专门化构造函数本身。

如果specialized构造函数可以工作,它可以是这样的:

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; }

};

错误:"模板Y::Y(const V*)"不能重载

但如前所述。。。我不知道这能不能做到。

为了显示潜在的问题,我将给出以下示例,该示例显示了依赖于基类中定义的特性的基类构造函数的不同使用。

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( )   {}
};

而不是

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

你可以添加一层:

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

然后

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

对于SFINAE,由于默认模板参数不是签名的一部分,

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; }

应该重写,例如:

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; }

在C++20中,requires可能会大大简化代码:

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; }
// ...
};
template<class...>struct types_tag{using type=types_tag;};
template<class...Ts>constexpr types_tag<Ts...> types{};

这些助手使您可以将许多类型作为一个bundle、一个参数进行传递。

现在你的类型X可以看起来像:

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>> {
};

X的用户传入X<T, types_tag<int, double, char>

您可以编写如下适配器:

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

其中我们使using别名具有比实现struct更好的名称。

作为一个类型传递的类型束可以使一整束元编程变得简单。你可以传递多个包裹;并且您可以通过值将types_tag传递给函数作为参数,以便于推断捆绑包的内容。