使用"std::enable_if"时,如何避免写入"::value"和&quo

How can I avoid writing `::value` and `::type` when using `std::enable_if`? [cppx]

本文关键字:quot 何避免 quo value if enable std 使用      更新时间:2023-10-16

注意:这是一个带答案的问题,目的是记录其他人可能觉得有用的技术,并可能了解其他人;甚至更好的解决方案。请随意添加评论或问题作为评论。也可以随意添加其他答案。:)


在我的一些代码中,即标题rfc/cppx/text/String.h中,我发现了以下神秘的片段:

template< class S, class enable = CPPX_IF_( Is_a_< String, S > ) >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

operator!支持具有到原始指针的隐式转换的String类。因此,我为这个类和派生类重载(以及其他)operator!,这样无意中使用了一个不受支持的运算符将产生适当的编译错误,并指出它是无意义的和不可访问的。我认为这比这样的用法被默默接受并带来意想不到的错误结果要好得多。

CPPX_IF_宏支持Visual C++12.0(2013)及更早版本,这使得C++11using在很大程度上超出了它的能力范围;

template< class S, class enable = If_< Is_a_< String, S > > >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

这看起来像std::enable_if

template< class S, class enabled = typename std::enable_if< Is_a_< String, S >::value, void >::type >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

除了If_CPPX_IF_及其表达式更加简洁易读之外。

我到底是怎么做到的?

在C++14中,变量模板使类型特征看起来更容易。将其与C++11模板别名结合起来,所有的cruft都消失了:

template <typename A, typename B>
bool is_base_of_v = std::is_base_of<A, B>::value;
template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

用法:

template <typename B, typename D>
enable_if_t<is_base_of_v<B, D>, Foo> some_function(B & b, D & d) { /* ... */ }

事实上,_t形式的"类型"别名计划作为C++14标准库的一部分,请参见[meta.Type.synop]。

一个舒适的带有C++11-using编译器的工具只是…

namespace cppx {
using std::enable_if;
template< class Condition_type, class Result_type = void >
using If_ = typename enable_if<Condition_type::value, Result_type>::type;
}  // namespace cppx

对更具using挑战性的编译器(如Visual C++12.0及更早版本)的支持(它理解using的基本用法,但使用上下文中包含的enable_if之类的内容越多,它就变得越来越不可靠)有点复杂,构建在像…

namespace cppx {
using std::enable_if;
template<
class Condition_type,
class Result_type = void,
class enabled = typename enable_if<Condition_type::value, void>::type 
>
struct If_T_
{
typedef Result_type     T;
typedef Result_type     type;
};
}  // namespace cppx

这基本上只提供了一个更可读的名称,并在一定条件下省去了::value。为了也省去typename::type,我使用了一个宏。但是,由于表达式通常是模板表达式,预处理器可能会将逗号解释为参数分隔符,因此预处理器可能看到多个参数。

我使用的解决方案(C++03的时间对我来说已经结束了)是使用C99/C++11可变宏,…

#define CPPX_IF_( ... ) 
typename cppx::If_T_< __VA_ARGS__ >::T

可以定义相应的宏以在没有typename的情况下使用该功能。


完整列表,文件rfc/cppx/utility/If_.h:

#pragma once
// Copyright (c) 2013 Alf P. Steinbach
#include <type_traits>      // std::enable_if
#define CPPX_IF_( ... ) 
typename cppx::If_T_< __VA_ARGS__ >::T
namespace cppx {
using std::enable_if;
template< class Condition_type, class Result_type = void >
using If_ = typename enable_if<Condition_type::value, Result_type>::type;
template<
class Condition_type,
class Result_type = void,
class enabled = typename enable_if<Condition_type::value, void>::type 
>
struct If_T_
{
typedef Result_type     T;
typedef Result_type     type;
};
}  // namespace cppx

此外,为了完整性,Is_a_被简单地定义为…

template< class Base, class Derived_or_eq >
using Is_a_ = std::is_base_of<Base, Derived_or_eq>;

这是Visual C++12.0所理解的CCD_ 21的用法。


为了能够使用复合条件而不在任何地方写入::value,以下定义派上了用场。本质上,这些是对类型进行操作的布尔运算符。也许值得注意的是,特别是一般的异或运算符,它不是用二进制XOR(例如!=)实现的:它会产生一个检查奇数个true值的运算符,除了正好有两个自变量的特殊情况外,它几乎没有实用性。

namespace cppx {
using std::integral_constant;
template< bool c >
using Bool_ = integral_constant<bool, c>;
using False = Bool_<false>;     // std::false_type;
using True  = Bool_<true>;      // std::true_type;
template< bool v, class First, class... Rest >
struct Count_
{
enum{ value = Count_<v, First>::value + Count_<v, Rest...>::value };
};
template< bool v, class X >
struct Count_<v, X>
{
enum{ value = int(!!X::value == v) };
};
template< class X >
using Not_ = Bool_<Count_<true, X>::value == 0>;                   // NOT
template< class... Args >
using All_ = Bool_<Count_<false, Args...>::value == 0>;            // AND
template< class... Args >
using Some_ = Bool_<Count_<true, Args...>::value != 0>;     // General inclusive OR.
template< class... Args >
using Either_ = Bool_<Count_<true, Args...>::value == 1>;   // General exclusive OR.
}  // namespace cppx

免责声明:这些代码都没有经过广泛的测试,C++编译器在模板元编程领域的怪癖很常见。

我们有C++03、C++11和C++14解决方案,但缺少Concepts Lite:

template <typename Derived, typename Base>
constexpr bool Is_a_() {
return std::is_base_of<Base, Derived>::value;
}
template<Is_a_<String> S>
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }

或者更简洁的:

template <typename Derived, typename Base>
concept bool Is_a_() {
return std::is_base_of<Base, Derived>::value;
}
void operator! ( Is_a_<String> const& )
{ string_detail::Meaningless::operation(); }

我强烈建议浏览一下Concepts Lite论文的教程(第2节),了解在我们摆脱enable_if霸主之后,世界会变得多么美好。