While I do like Barry's approach, I don't like that the solution imposes to be more generic than it actually is (it can't support templates that take any number of template parameters as well as non-type template parameters). Though, one easy way to prevent this is to make the concept customized to just vectors (e.g. SpecializesVector
).
Another way to go about this, is to take advantage of the fact that all of std::vector
's template parameters are accessible through its publicly available member types value_type
and allocator_type
. Allowing us to write something like:
template<typename T>
using as_vector = std::vector<
typename T::value_type,
typename T::allocator_type>;
template<typename T>
concept vector = std::same_as<T, as_vector<T>>;
template<typename T>
concept has_vector = vector<decltype(std::declval<T>().vec())>;
And, if the we want the value_type
of the vector be arithmetic, we could write something like this:
template<typename T>
concept arithmetic = std::integral<T> or std::floating_point<T>;
template<typename T>
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;
template<typename T>
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;
While I also think that 康桓瑋's solution looks very neat and concise, using templated lambda's inside of a requires-clause actually makes potential error messages really unclear and hard to understand (which is supposed to be one the strong points of concepts). For example, when checking a type that has a member-function vec()
which returns a std::array
instead of a std::vector
, the error messsage produced by Clang would be something like:
<source>:37:1: error: static assertion failed
static_assert(has_vector<D>);
^ ~~~~~~~~~~~~~
<source>:37:15: note: because 'D' does not satisfy 'has_vector'
static_assert(has_vector<D>);
^
<source>:25:5: note: because '[]<arithmetic U, typename A>(std::vector<U, A>) {
}(t.vec())' would be invalid: no matching function for call to object of type
'(lambda at <source>:25:5)'
[]<arithmetic U, typename A>(std::vector<U, A>){}(t.vec());
^
As opposed to:
<source>:37:1: error: static assertion failed
static_assert(has_vector<D>);
^ ~~~~~~~~~~~~~
<source>:37:15: note: because 'D' does not satisfy 'has_vector'
static_assert(has_vector<D>);
^
<source>:21:22: note: because 'decltype(std::declval<D>().vec())'
(aka 'array<int, 3>') does not satisfy 'arithmetic_vector'
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;
^
<source>:18:29: note: because 'std::array<int, 3>' does not satisfy 'vector'
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;
^
<source>:10:1: note: because substituted constraint expression is ill-formed:
no type named 'allocator_type' in 'std::array<int, 3>'
And—while using the concept with the templated lambda—when we check a type with a vec()
member-function that returns a std::vector
that doesn't have an arithmetic value_type
, we get the exact same error message as before:
<source>:36:1: error: static assertion failed
static_assert(has_vector<C>);
^ ~~~~~~~~~~~~~
<source>:36:15: note: because 'C' does not satisfy 'has_vector'
static_assert(has_vector<C>);
^
<source>:25:5: note: because '[]<arithmetic U, typename A>(std::vector<U, A>) {
}(t.vec())' would be invalid: no matching function for call to object of type
'(lambda at <source>:25:5)'
[]<arithmetic U, typename A>(std::vector<U, A>){}(t.vec());
^
As opposed to:
<source>:36:1: error: static assertion failed
static_assert(has_vector<C>);
^ ~~~~~~~~~~~~~
<source>:36:15: note: because 'C' does not satisfy 'has_vector'
static_assert(has_vector<C>);
^
<source>:21:22: note: because 'decltype(std::declval<C>().vec())'
(aka 'vector<char *>') does not satisfy 'arithmetic_vector'
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;
^
<source>:18:43: note: because 'typename vector<char *>::value_type'
(aka 'char *') does not satisfy 'arithmetic'
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;
^
<source>:7:22: note: because 'char *' does not satisfy 'integral'
concept arithmetic = std::integral<T> or std::floating_point<T>;
^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:100:24:
note: because 'is_integral_v<char *>' evaluated to false
concept integral = is_integral_v<_Tp>;
^
<source>:7:42: note: and 'char *' does not satisfy 'floating_point'
concept arithmetic = std::integral<T> or std::floating_point<T>;
^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:109:30:
note: because 'is_floating_point_v<char *>' evaluated to false
concept floating_point = is_floating_point_v<_Tp>;
^
template<typename T, concept C=Anything> concept HasVector = requires
could exist. – Phonetician