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

What is the recommended way to simulate concepts and constraints?

本文关键字:方法 是什么 约束 模拟      更新时间:2023-10-16

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

  • 使用static_assert

    template <typename T, typename U>
    void order(T& a, U& b)
    {
    static_assert(LessThanComparable<U,T>, "oh this is not epic");
    if (b < a)
    {
    using std::swap;
    swap(a, b);
    }
    }
    

    这种方法不适用于函数重载。

  • 使用typename = enable_if

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

    如果一个过于"聪明"的家伙手动指定了第三个参数呢?

  • 在功能原型中使用enable_if

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

    有时在函数重载中也不起作用。

  • 使用enable_if作为伪非类型模板参数的类型

    template <typename T, typename U,
    std::enable_if_t<LessThanComparable<U,T>>, void*> = nullptr> // or int = 0
    void order(T& a, U& b)
    {
    if (b < a)
    {
    using std::swap;
    swap(a, b);
    }
    }
    

    我以前看到过,我想不出有什么缺点。

  • 以及许多其他变体。

哪些是首选或推荐的?优点和缺点是什么?感谢您的帮助。

这是一个复杂的主题,很难回答您的问题。

不管怎样,一些意见/建议,没有任何详尽的借口。

(1)static_assert()

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

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

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

(2) 你对的看法是正确的

typename = std::enable_if_t</* some test */>>

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

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

假设您有两个互补的foo()功能,必须通过SFINAE启用/禁用;第一个测试为真,第二个测试为假。

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

/* 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 */ }

错误:这个解决方案根本不起作用,因为您启用/禁用的不是第二个typename,而是第二个type的默认值。因此,您并没有完全启用/禁用函数,编译器必须考虑具有相同签名的两个函数(函数的签名不取决于默认值);因此您发生了冲突并获得了错误。

以下解决方案,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> 

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

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

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>{}); }

当条件为true时,LessThanComplarablestd::true_type继承,当条件为false时,从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 ?
}
}

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

否则,使用它。

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

还有一种方法可以使用模板别名来实现类似于使用sfinae的别名模板的概念:语言允许吗?

而且,您在列表中错过了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);
}
}