Introduction
C++ Concept是C++20的四个重大更新的一部分(其他三个分别是Module, Coroutine 和 Format),取代了经典的SFINAE,使得模板的约束更加明朗且可以复用。
取代enable_if_t
原代码如下
enum class NumType
{
NONE,
INTEGER,
FLOAT
};
template<class, class = void>
struct get_type
{
static constexpr NumType type = NumType::NONE;
};
template<class T>
struct get_type<T ,enable_if_t<is_integral_v<T>>>
{
static constexpr NumType type = NumType::INTEGER;
};
template<class T>
struct get_type<T, enable_if_t<is_floating_point_v<T>>>
{
static constexpr NumType type = NumType::FLOAT;
};
template<typename Ty>
constexpr auto get_type_v = get_type<Ty>::type;
可以被替换为
template<class>
struct get_type_c
{
static constexpr auto type = NumType::NONE;
};
template<class Ty> requires is_floating_point_v<Ty>
struct get_type_c<Ty>
{
static constexpr auto type = NumType::FLOAT;
};
template<class Ty> requires is_integral_v<Ty>
struct get_type_c<Ty>
{
static constexpr auto type = NumType::INTEGER;
};
template<class Ty>
constexpr auto get_type_c_v = get_type_c<Ty>::type;
取代void_t
原代码如下
#define has_sth_sfinae(func) \
\
template<class Ty, class = void> \
struct has_##func : false_type \
{ \
\
}; \
\
template<class Ty> \
struct has_##func<Ty, void_t<decltype(declval<Ty>().func())>> : true_type \
{ \
\
}; \
\
template< class Ty > \
inline constexpr auto has_##func##_v = has_##func##<Ty>::value; \
\
template< class Ty > \
inline constexpr auto has_not_##func##_v = !has_##func##<Ty>::value
has_sth_sfinae(to_string);
has_sth_sfinae(print);
template <class help_dbg, class = enable_if_t<has_not_to_string_v< help_dbg > && has_print_v<help_dbg>>>
void debug_print(help_dbg&& object)
{
cout << object.to_string() << '\n';
}
template <class help_dbg, class = enable_if_t<has_to_string_v< help_dbg>&& has_not_print_v<help_dbg>>>
void debug_print(help_dbg&& object)
{
object.print();
}
可以被替换为
template<typename T>
concept HasToString = requires (T && t) { t.to_string(); };
template<typename T>
concept HasPrint = requires (T && t) { t.print(); };
template<typename T>
concept HasNotBoth = 1 >= (HasPrint<T> + HasToString<T>);
template<typename T> requires HasNotBoth<T>
void print(T&& t)
{
if constexpr (HasToString<T>)
cout << t.to_string() << '\n';
else if constexpr (HasPrint<T>)
t.print();
else
cout << "have nothing\n";
}
更多语法
匿名requires
对于之前的print和to_string两个函数同时出现的时候还没法处理,在这里处理以下
template<class T>
void print(T &&) requires (requires (T&& t) { t.print(); }) && (requires (T&& t) { t.to_string(); })
{
cout << "have both!!\n";
}
当你不想声明一个concept的时候就可以直接使用requires来定义一个匿名concept,因为concept本身就等于一个requires表达式。
typename<class...>
concept xx = requires ...
上面那样写依然很臃肿,所以我们把它们写在一起
template<class T>
void print(T&&) requires requires (T&& t) { t.print(); t.to_string(); }
{
cout << "have both!!\n";
}
也许你注意到了,之前的requires在模板声明后面,现在为什么在函数后面?
这是因为C++为了迎合不同习惯,让编译器认为这两种写法是等效的。
套娃
还记得我们已经定义了HasToString了吗?我们可以使用它们,我们改造之前的代码如下
template<class T>
void print(T&&) requires requires { requires HasPrint<T>; requires HasToString<T>; }
{
cout << "have both!!\n";
}
后面的scope可以接受参数,比如我们可以这样写
template<class T>
void print(T&&) requires requires(T&& t) { requires HasPrint<decltype(t)>; requires HasToString<decltype(t)>; }
{
cout << "have both!!\n";
}
表达式转换
concept还有一个更美好的特性,就是requires表达式内可以塞一个箭头,这代表着后面模板结构体/类的第一个参数的填充,比如我限定print的返回值必须是void(这里还有一层语义,就是t必须存在print方法)
{ t.print() } -> same_as<void>;
如果我们用type_traits就是这样
same_as < invoke_result_t< decltype (decltype(X{})::print), X >, void >
很好,那么我们把之前的have both函数约束以下
template<class T> requires requires(T&& t)
{ { t.print() } -> same_as<void>; {t.to_string()} -> convertible_to<const char*>;}
void print(T&&)
{
cout << "have both!!\n";
}
练习
现在看下面的复杂concept应该是容易且直观的
template <class T>
concept Semiregular = DefaultConstructible<T> &&
CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
requires(T a, size_t n) {
requires Same<T*, decltype(&a)>; // nested: "Same<...> evaluates to true"
{ a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw
requires Same<T*, decltype(new T)>; // nested: "Same<...> evaluates to true"
requires Same<T*, decltype(new T[n])>; // nested
{ delete new T }; // compound
{ delete new T[n] }; // compound
};