模拟概念和约束的推荐方法是什么?

What is the recommended way to simulate concepts and constraints?

在介绍概念和约束之前,有几种方法可以模拟这种编译时检查。以“order()”函数为例:(如何在没有概念或约束的情况下实现LessThanComparable是另一回事)

哪些是首选或推荐的?有什么好处和坏处? 感谢您的帮助。

至少在某些版本的标准中不允许 void* 类型的非类型模板参数;我会使用 bool 和值 =true.

否则,使用那个。

这是一个复杂的话题,很难回答你的问题。

无论如何,一些observations/suggestions,没有任何假装是详尽无遗的。

(1) static_assert() 方式

static_assert(LessThanComparable<U,T>, "oh this is not epic");

如果您想要一个仅适用于某些类型的函数并在使用错误的类型调用时给出错误(一个明确的错误,因为您可以选择错误消息),这是一个很好的解决方案。

但当您需要替代方案时,通常是错误的解决方案。不是 SFINAE 解决方案。因此,使用错误类型的参数调用函数会产生错误,并且不允许在替换中使用另一个函数。

(2) 关于

你是对的
typename = std::enable_if_t</* some test */>>

解决方案。用户可以手动显式显示第三个参数。开玩笑,我说这个解可以"hijacked".

但这不是此解决方案的唯一缺点。

假设您有两个互补的 foo() 函数,它们必须通过 SFINAE enabled/disabled;第一个测试为真,第二个测试为假。

你可以认为以下解决方案很危险(可以被劫持)但可以工作

/* true case */
template <typename T, typename = std::enable_if_t<true == some_test_v<T>>>
void foo (T t)
 { /* something with t */ }

/* false case */
template <typename T, typename = std::enable_if_t<false == some_test_v<T>>>
void foo (T t)
 { /* something with t */ }

错误:此解决方案根本行不通,因为您 enabling/disabling 不是第二个类型名,而只是第二个类型名的默认值。所以你不完全 enabling/disabling 函数,编译器必须考虑两个具有相同签名的函数(函数的签名不依赖于默认值);所以你有一个碰撞并得到一个错误。

以下解决方案,SFINAE遍历返回类型

std::enable_if_t<LessThanComparable<U,T>, void> order(T& a, U& b)

(也没有void,即默认类型

std::enable_if_t<LessThanComparable<U,T>> order(T& a, U& b)

) 或超过第二种类型(遵循 Yakk 关于允许的非标准 void *)

的建议
template <typename T, typename U,
          std::enable_if_t<LessThanComparable<U,T>>, bool> = true> 

是(恕我直言)好的解决方案,因为它们都避免了劫持风险,并且与两个具有相同名称和签名的互补函数兼容。

我建议第三种可能的解决方案(不可劫持,互补兼容),即使用 SFINAE enabled/disabled 类型添加第三个默认值:something as

template <typename T, typename U>
void order(T& a, U& b, std::enable_if_t<LessThanComparable<U,T>> * = nullptr)

另一种可能的解决方案完全避免使用 SFINAE,但使用标签调度;像

template <typename T, typename U>
void order_helper (T & a, U & b, std::true_type const &)
 { if (b < a) { std::swap(a, b); } }

// something different if LessThanComparable is false ?
template <typename T, typename U>
void order_helper (T & a, U & b, std::false_type const &)
 { /* ???? */ }

template <typename T, typename U>
void order (T & a, U & b)
 { order_helper(a, b, LessThanComparable<U,T>{}); }

如果条件为真时 LessThanComplarablestd::true_type 继承,当条件为假时从 std::false_type 继承。

否则,如果LessThanComparable只给出一个布尔值,调用order_helper()可以是

order_helper(a, b, std::integral_constant<bool, LessThanComparable<U,T>>{});

(3) 如果你能用C++17,有if constexpr的方法可以避免很多重载

template <typename T, typename U>
void order(T& a, U& b)
{
   if constexpr ( LessThanComparable<U, T> )
    { 
      if ( b < a )
         std::swap(a, b);
    }
   else
    {
      // what else ?
    }
}

您应该看看 range-v3 库如何模拟概念 https://github.com/ericniebler/range-v3/blob/master/include/range/v3/range_concepts.hpp

还有一种方法是使用模板别名来实现类似概念的东西

而且,您错过了列表中的 decltype 个变体:

template <typename T, typename U>
auto order(T& a, U& b) -> decltype(void(b < a))
{
    if (b < a)
    {
        using std::swap;
        swap(a, b);
    }
}

template <typename T, typename U,
    typename = decltype(void(b < a))>
void order(T& a, U& b)
{
    if (b < a)
    {
        using std::swap;
        swap(a, b);
    }
}