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

Posted

技术标签:

【中文标题】是否可以混合 SFINAE 和模板专业化?【英文标题】:Is it possible to mix SFINAE and template specialisation? 【发布时间】:2020-10-13 01:55:58 【问题描述】:

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

// 声明
模板
结构 ArgsEstimate;

// 字符串的特化,SFINAE 将是矫枉过正
模板
结构 ArgsEstimate<:string args...> 
    静态常量 std::size_t 大小 = 64 + ArgsEstimate::size;
;

// 算术类型的特化
模板::value>::type* = nullptr,
         类型名... Args>
结构 ArgsEstimate 
    静态常量 std::size_t 大小 = sizeof(AirthmeticT) + ArgsEstimate::size;
;

// 指针类型的特化
模板::value>::type* = nullptr,
         类型名... Args>
结构 ArgsEstimate 
    静态常量 std::size_t 大小 = 32 + ArgsEstimate::size;
;

问题是这段代码在我已经完成enable_if 的点上给出了编译错误“模板参数在部分特化中不可推导”。结构中的static_assert 也不起作用,因为会重新定义。

我知道,我可以单独使用 SFINAE 和函数重载来做到这一点。但是,对于像 std::string 这样的情况,使用 SFINAE 是大材小用。

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

【问题讨论】:

【参考方案1】:

直接回答您的问题

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

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

这行得通……有点。您只需要确保第二个参数始终为 void:

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

ArgsEstimate<int, int, int> wrong; // oups, will use the base template.

这是不切实际的。

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

前置概念解决方案

您需要做的是将您的班级分成两个班级。只为 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;
;
// 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;
;

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

template<class... Args>
struct ArgsEstimate

    static const std::size_t size = (... + ArgEstimate<Args>::size);
;

还只是想指出,指针不需要 SFINAE:

// specialisation for pointer types
template <class T, class... Args>
struct ArgsEstimate<T*, Args...> 
    static const std::size_t size = 32 + ArgsEstimate<Args...>::size;
;

【讨论】:

以上是关于是否可以混合 SFINAE 和模板专业化?的主要内容,如果未能解决你的问题,请参考以下文章

功能模板专业化的重要性和必要性

矢量类型的模板类专业化 - 不同的有效语法?

模板函数重载和 SFINAE 实现

SFINAE 独一无二

模板变量 C 数组完全专业化是不是应该指定数组大小?

类模板构造函数中的 SFINAE