模拟概念和约束的推荐方法是什么?
What is the recommended way to simulate concepts and constraints?
在介绍概念和约束之前,有几种方法可以模拟这种编译时检查。以“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);
}
}
如果一个过"intelligent"的家伙手动指定了第三个参数怎么办?
在函数原型中使用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);
}
}
之前看过这个,想不出有什么缺点
以及许多其他变体。
哪些是首选或推荐的?有什么好处和坏处?
感谢您的帮助。
至少在某些版本的标准中不允许 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>{}); }
如果条件为真时 LessThanComplarable
从 std::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);
}
}
在介绍概念和约束之前,有几种方法可以模拟这种编译时检查。以“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); } }
如果一个过"intelligent"的家伙手动指定了第三个参数怎么办?
在函数原型中使用
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); } }
之前看过这个,想不出有什么缺点
以及许多其他变体。
哪些是首选或推荐的?有什么好处和坏处? 感谢您的帮助。
至少在某些版本的标准中不允许 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>{}); }
如果条件为真时 LessThanComplarable
从 std::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);
}
}