没有特定成员的类型的 SFINAE
SFINAE for types that don't have particular members
为什么以下代码无法编译?为具有特定成员的类型启用模板的最简洁的解决方案是什么?如果模板变量被直接用于初始化它的表达式替换,它也会编译。
#include <iostream>
template<class T>
constexpr bool is_rect = std::is_same_v<decltype(T::left, T::top, T::right, T::bottom, void()), void>;
// compiles if is_rect<T> is replaced with the expression directly, but of course it's not a solution
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
template<class T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
void f(T)
{
std::cout << "arithmetic enabled\n";
}
struct ActualType { float left, top, right, bottom; };
int main()
{
f(ActualType{});
f(int{});
return 0;
}
问题是 is_rect<T>
总是被指定为 std::enable_if_t
的模板参数作为 f()
签名的一部分,并且当 T
没有时它是无效的表达式'没有特定的成员。
您可以应用偏特化,使 is_rect
根据类型是否具有特定成员给出 true
或 false
。例如
template<class T, class = void>
constexpr bool is_rect = false;
template<class T>
constexpr bool is_rect<T, std::void_t<decltype(T::left, T::top, T::right, T::bottom)>> = true;
然后
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
您的 is_rect<T>
的问题是,如果 T
缺少任何数据成员,则定义是一个格式错误的表达式,而不是计算结果为的格式正确的表达式假的。
您可以使用 is_detected
习惯用法来测试您的模板是否格式正确:
在 godbolt.org 上试用:Demo
#include <type_traits>
namespace detail
{
template<class AlwaysVoid, template <class...> class Op, class... Args>
struct detector : std::false_type {};
template<template <class...> class Op, class... Args>
struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
} // namespace detail
template<template <class...> class Op, class... Args>
constexpr bool is_detected_v = detail::detector<void, Op, Args...>::value;
namespace detail
{
template<class T>
using rect_detector = decltype(T::left, T::top, T::right, T::bottom);
} // namespace detail
template<class T>
constexpr bool is_rect = is_detected_v<detail::rect_detector, T>;
在 C++20 中,concepts 是为具有特定成员的类型启用模板的最简洁方式:
在 godbolt.org 上试用:Demo
#include <iostream>
#include <type_traits>
template<class T>
concept arithmetic = std::is_arithmetic_v<T>;
template<class T>
concept rect = requires (T t)
{
{ t.left };
{ t.top };
{ t.right };
{ t.bottom };
};
template<rect T>
void f(T)
{
std::cout << "rect enabled\n";
}
template<arithmetic T>
void f(T)
{
std::cout << "arithmetic enabled\n";
}
为什么以下代码无法编译?为具有特定成员的类型启用模板的最简洁的解决方案是什么?如果模板变量被直接用于初始化它的表达式替换,它也会编译。
#include <iostream>
template<class T>
constexpr bool is_rect = std::is_same_v<decltype(T::left, T::top, T::right, T::bottom, void()), void>;
// compiles if is_rect<T> is replaced with the expression directly, but of course it's not a solution
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
template<class T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
void f(T)
{
std::cout << "arithmetic enabled\n";
}
struct ActualType { float left, top, right, bottom; };
int main()
{
f(ActualType{});
f(int{});
return 0;
}
问题是 is_rect<T>
总是被指定为 std::enable_if_t
的模板参数作为 f()
签名的一部分,并且当 T
没有时它是无效的表达式'没有特定的成员。
您可以应用偏特化,使 is_rect
根据类型是否具有特定成员给出 true
或 false
。例如
template<class T, class = void>
constexpr bool is_rect = false;
template<class T>
constexpr bool is_rect<T, std::void_t<decltype(T::left, T::top, T::right, T::bottom)>> = true;
然后
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
您的 is_rect<T>
的问题是,如果 T
缺少任何数据成员,则定义是一个格式错误的表达式,而不是计算结果为的格式正确的表达式假的。
您可以使用 is_detected
习惯用法来测试您的模板是否格式正确:
在 godbolt.org 上试用:Demo
#include <type_traits>
namespace detail
{
template<class AlwaysVoid, template <class...> class Op, class... Args>
struct detector : std::false_type {};
template<template <class...> class Op, class... Args>
struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
} // namespace detail
template<template <class...> class Op, class... Args>
constexpr bool is_detected_v = detail::detector<void, Op, Args...>::value;
namespace detail
{
template<class T>
using rect_detector = decltype(T::left, T::top, T::right, T::bottom);
} // namespace detail
template<class T>
constexpr bool is_rect = is_detected_v<detail::rect_detector, T>;
在 C++20 中,concepts 是为具有特定成员的类型启用模板的最简洁方式:
在 godbolt.org 上试用:Demo
#include <iostream>
#include <type_traits>
template<class T>
concept arithmetic = std::is_arithmetic_v<T>;
template<class T>
concept rect = requires (T t)
{
{ t.left };
{ t.top };
{ t.right };
{ t.bottom };
};
template<rect T>
void f(T)
{
std::cout << "rect enabled\n";
}
template<arithmetic T>
void f(T)
{
std::cout << "arithmetic enabled\n";
}