没有特定成员的类型的 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 根据类型是否具有特定成员给出 truefalse。例如

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

LIVE

您的 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";
}