tartanllama Detection Idiom - A Stopgap for Concepts
NOTE:
1、"Stopgap"的含义是"临时替代品"
Concepts is a proposed C++ feature which allows succinct, expressive, and powerful constraining of templates. They have been threatening to get in to C++ for a long time now, with the first proposal being rejected for C++11. They were finally merged in to C++20 a few months ago, which means we need to hold on for another few years before they’re in the official standard rather than a Technical Specification. In the mean time, there have been various attempts to implement parts of concepts as a library so that we can have access to some of the power of Concepts without waiting for the new standard. The detection idiom – designed by Walter Brown and part of the Library Fundamentals 2 Technical Specification – is one such solution. This post will outline the utility of it, and show the techniques which underlie its implementation.
A problem
template <class T>
int calculate_foo_factor (const T& t);
struct has_special_support {
int get_foo() const;
};
struct will_need_generic_calculation {
// no get_foo member function
};
Using C++20 concept
Using concepts we could write calculate_foo_factor
like so:
template <class T>
concept bool SupportsFoo = requires (T t) {
{ t.get_foo() } -> int;
};
template <SupportsFoo T>
int calculate_foo_factor (const T& t) {
return t.get_foo();
}
template <class T>
int calculate_foo_factor (const T& t) {
// insert generic calculation here
return 42;
}
This is quite succinct and clear: SupportsFoo
is a concept which checks that we can call get_foo
on t
with no arguments, and that the type of that expression is int
. The first calculate_foo_factor
will be selected by overload resolution for types which satisfy the SupportsFoo
concept, and the second will be chosen for those which don’t.
Unfortunately, our library has to support C++14. We’ll need to try something different. I’ll demonstrate a bunch of possible solutions to this problem in the next section. Some of them may seem complex if you aren’t familiar with the metaprogramming techniques used, but for now, just note the differences in complexity and abstraction between them. The metaprogramming tricks will all be explained in the following section.
Solutions
namespace detail {
template <class T>
auto calculate_foo_factor (const T& t, int)
-> decltype(t.get_foo()) {
return t.get_foo();
}
template <class T>
int calculate_foo_factor (const T& t, ...) {
// insert generic calculation here
return 42;
}
}
template <class T>
int calculate_foo_factor (const T& t) {
return detail::calculate_foo_factor(t, 0);
}
Well, it works, but it’s not exactly clear. What’s the int
and ...
there for? Why do we need an extra overload? The answers are not the important part here; what is important is that unless you’ve got a reasonable amount of metaprogramming experience, it’s unlikely you’ll be able to write this code offhand, or even copy-paste it without error.
The code could be improved by abstracting out the check for the presence of the member function into its own metafunction:
template <class... Ts>
using void_t = void;
template <class T, class=void>
struct supports_foo : std::false_type{};
template <class T>
struct supports_foo<T, void_t<decltype(std::declval<T>().get_foo())>>
: std::true_type{};
Again, some more expert-only template trickery which I’ll explain later. Using this trait, we can use std::enable_if
to enable and disable the overloads as required:
template <class T, std::enable_if_t<supports_foo<T>::value>* = nullptr>
auto calculate_foo_factor (const T& t) {
return t.get_foo();
}
template <class T, std::enable_if_t<!supports_foo<T>::value>* = nullptr>
int calculate_foo_factor (const T& t) {
// insert generic calculation here
return 42;
}
Metaprogramming demystified
Type traits and _v
and _t
suffixes
NOTE:
1、这部分内容,收录于
C++-name-convention
章节了