是否可以混合使用 SFINAE 和模板专业化?

tin*_*ast 0 c++ partial-specialization sfinae template-specialization

这是我大致想要实现的目标:

// 声明
模板<类型名称...参数>
结构ArgsEstimate;

// 字符串的专门化,SFINAE 就有点过分了
模板<类型名称...参数>
struct ArgsEstimate<std::string&, Args...> {
    静态常量 std::size_t 大小 = 64 + ArgsEstimate<Args...>::size;
};

// 算术类型的特化
模板<类型名称 AirthmeticT,
         类型名 std::enable_if<std::is_arithmetic<AirthmeticT>::value>::type* = nullptr,
         类型名称...参数>
struct ArgsEstimate<AirthmeticT, Args...> {
    static const std::size_t size = sizeof(AirthmeticT) + ArgsEstimate<Args...>::size;
};

// 指针类型的特化
模板<类型名 PtrT,
         类型名 std::enable_if<std::is_pointer<PtrT>::value>::type* = nullptr,
         类型名称...参数>
struct ArgsEstimate<PtrT, Args...> {
    静态常量 std::size_t 大小 = 32 + ArgsEstimate<Args...>::size;
};

问题是,这段代码在我所做的点上给出了编译错误“模板参数在部分专业化中不可推导”enable_if。结构内部的Astatic_assert也不起作用,因为将会重新定义。

我知道,我可以单独使用 SFINAE 和函数重载来做到这一点。然而,对于像这样的情况std::string,使用 SFINAE 就有点矫枉过正了。

所以我想知道是否有混合模板专业化和 SFINAE 的干净方法。

bol*_*lov 5

直接回答你的问题

你可以,但你真的不能。您的情况因可变参数模板参数而变得复杂。

// specialisation for arithmetic types
template<class AirthmeticT, class... Args>
struct ArgsEstimate<
    AirthmeticT,
    std::enable_if_t<std::is_arithmetic_v<AirthmeticT>>,
    Args...>
{
    static const std::size_t size = sizeof(AirthmeticT) + ArgsEstimate<Args...>::size;
};
Run Code Online (Sandbox Code Playgroud)

这有效......有点。您只需要确保第二个参数始终为空:

ArgsEstimate<int, void, /* ... */> ok; // will use the integer specialization

ArgsEstimate<int, int, int> wrong; // oups, will use the base template.
Run Code Online (Sandbox Code Playgroud)

这是不切实际的。

C++20 概念

概念优雅地解决了这个问题:

// specialisation for arithmetic types
template<class T, class... Args>
    requires  std::is_arithmetic_v<T>
struct ArgsEstimate<T, Args...>
{
    static const std::size_t size = sizeof(T) + ArgsEstimate<Args...>::size;
};
Run Code Online (Sandbox Code Playgroud)

预概念解决方案

您需要做的是将您的班级分成两个班级。仅定义 1 个参数的大小。在这里您可以使用 SFINAE。另一篇总结了它们:

template <class T, class Enable = void>
struct ArgEstimate {};

// specialisation for string, SFINAE would be overkill
template<>
struct ArgEstimate<std::string&>
{
    static const std::size_t size = 64;
};

// specialisation for arithmetic types
template<class T>
struct ArgEstimate<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
    static const std::size_t size = sizeof(T);
};

// specialisation for pointer types
template <class T>
struct ArgEstimate<T*>
{
    static const std::size_t size = 32;
};
Run Code Online (Sandbox Code Playgroud)
// the declaration
template<class... Args> struct ArgsEstimate;

template<class T>
struct ArgsEstimate<T>
{
    static const std::size_t size = ArgEstimate<T>::size;
};

template<class Head, class... Tail>
struct ArgsEstimate<Head, Tail...>
{
    static const std::size_t size = ArgEstimate<Head>::size + ArgsEstimate<Tail...>::size;
};
Run Code Online (Sandbox Code Playgroud)

如果你有 C++17,你可以使用折叠表达式来简化总和:

template<class... Args>
struct ArgsEstimate
{
    static const std::size_t size = (... + ArgEstimate<Args>::size);
};
Run Code Online (Sandbox Code Playgroud)

另外只是想指出您不需要 SFINAE 来获取指针:

// specialisation for pointer types
template <class T, class... Args>
struct ArgsEstimate<T*, Args...> {
    static const std::size_t size = 32 + ArgsEstimate<Args...>::size;
};
Run Code Online (Sandbox Code Playgroud)