是否可以混合使用 SFINAE 和模板专业化?
Is it possible to mix SFINAE and template specialisation?
这是我大致想要实现的目标:
// the declaration
template<typename... Args>
struct ArgsEstimate;
// specialisation for string, SFINAE would be overkill
template<typename... Args>
struct ArgsEstimate<std::string&, Args...> {
static const std::size_t size = 64 + ArgsEstimate<Args...>::size;
};
// specialisation for arithmetic types
template<typename AirthmeticT,
typename std::enable_if<std::is_arithmetic<AirthmeticT>::value>::type* = nullptr,
typename... Args>
struct ArgsEstimate<AirthmeticT, Args...> {
static const std::size_t size = sizeof(AirthmeticT) + ArgsEstimate<Args...>::size;
};
// specialisation for pointer types
template<typename PtrT,
typename std::enable_if<std::is_pointer<PtrT>::value>::type* = nullptr,
typename... Args>
struct ArgsEstimate<PtrT, Args...> {
static const std::size_t size = 32 + ArgsEstimate<Args...>::size;
};
问题是这段代码给出了一个编译错误 “模板参数在部分特化中不可推导” 在我已经完成的地方 enable_if
。结构内部的 static_assert
也不起作用,因为将重新定义。
我知道,我可能可以单独使用 SFINAE 和函数重载来完成此操作。但是,对于像 std::string
这样的情况,使用 SFINAE 就有点矫枉过正了。
所以我想知道是否有混合模板专业化和 SFINAE 的干净方法。
直接回答您的问题
你可以,但你真的不能。您的情况因可变参数模板参数而变得复杂。
// 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;
};
预概念解决方案
您需要做的是将您的 class 分成两个 class。一个只为 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;
};
这是我大致想要实现的目标:
// the declaration template<typename... Args> struct ArgsEstimate; // specialisation for string, SFINAE would be overkill template<typename... Args> struct ArgsEstimate<std::string&, Args...> { static const std::size_t size = 64 + ArgsEstimate<Args...>::size; }; // specialisation for arithmetic types template<typename AirthmeticT, typename std::enable_if<std::is_arithmetic<AirthmeticT>::value>::type* = nullptr, typename... Args> struct ArgsEstimate<AirthmeticT, Args...> { static const std::size_t size = sizeof(AirthmeticT) + ArgsEstimate<Args...>::size; }; // specialisation for pointer types template<typename PtrT, typename std::enable_if<std::is_pointer<PtrT>::value>::type* = nullptr, typename... Args> struct ArgsEstimate<PtrT, Args...> { static const std::size_t size = 32 + ArgsEstimate<Args...>::size; };
问题是这段代码给出了一个编译错误 “模板参数在部分特化中不可推导” 在我已经完成的地方 enable_if
。结构内部的 static_assert
也不起作用,因为将重新定义。
我知道,我可能可以单独使用 SFINAE 和函数重载来完成此操作。但是,对于像 std::string
这样的情况,使用 SFINAE 就有点矫枉过正了。
所以我想知道是否有混合模板专业化和 SFINAE 的干净方法。
直接回答您的问题
你可以,但你真的不能。您的情况因可变参数模板参数而变得复杂。
// 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;
};
预概念解决方案
您需要做的是将您的 class 分成两个 class。一个只为 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;
};