Consider any of the common type-level algorithms provided by libraries such as Boost.MP11, Brigand, etc...
For instance:
template<typename... Args>
struct TypeList;
using my_types = TypeList<int, float, char, float, double>;
constexpr int count = boost::mp11::mp_count_if<my_types, std::is_floating_point>::value;
// this holds:
static_assert(count == 3);
Notice that std::is_floating_point could be defined as:
template<typename T>
struct is_floating_point { constexpr bool value = __compiler_magic(T); };
And likewise, we have the std::floating_point concept
template<typename T>
concept floating_point = requires (T t) { __other_compiler_magic(T); };
Sadly, despite the similarity, there does not seem to be an easy way to write something like this without introducing a manually-named wrapper for the concept:
constexpr int count = boost::mp11::count_if<my_types, std::floating_point>::value;
My question is: why cannot concepts be passed in place of types at this point ? Is it a lack of standardization, or is it something that these libraries can solve by providing more overloads ?
It looks like every concept has to be wrapped in a templated type which will just call the concept on its template argument.
From the outside, concepts just look like meta-functions whose domain is {set of types} -> bool. Compilers are able to delay passing parameters to "traditional" type-based metafunctions such as std::is_floating_point, why can't the same seem to happen with concepts ?
The literal answer is that we have template template parameters but not concept template parameters, so you can't pass a concept as a template argument.
The other literal answer is that it was never part of the original concepts proposal and nobody has put in the effort to suggest it as an extension (although I've been collecting use-cases).
One thing that would have to be answered is how dependent concepts affect subsumption - since currently use of concepts is never dependent and so figuring out subsumption is straightforward (actually, it's still not straightforward at all, but at least all the things you need are right there). But in a scenario like:
template <template <typename> concept C, typename T>
requires C<T>
void foo(T); // #1
template <typename T>
void foo(T); // #2
Probably if #1 is viable, you want to say it's a beter candidate than #2 since it's still constrained while the other is not. Maybe that's trivial. But then:
template <template <typename> concept C, typename T>
requires C<T>
void bar(T); // #3
template <OtherConcept T>
void bar(T); // #4
Let's say #3 and #4 are both viable, is it possible to say which is better? We generally say a whole overload is always better than a different one - but that might not be the case here. Maybe this is just ambiguous?
That seems to me like the main question that would need to be answered in order to get concept template parameters.
The other question might be, can I write foo<convertible_to<int>>(42). convertible_to<int> isn't really a unary concept, but it is a type-constraint that is treated as one in certain contexts, so I would still expect that to work.
Once we have such a thing, I'm sure Boost.Mp11 will quickly acquire something like:
template <template <typename...> concept C>
struct mp_quote_c {
template <typename... T>
using fn = mp_bool<C<T...>>;
};
So that you can write:
constexpr int count = mp_count_if_q<my_types, mp_quote_c<std::floating_point>>::value;
Related
In C++ you can make concepts that check for specific type equality:
template<typename T> concept Int = std::is_same_v<T, int>;
template<typename T> concept String = std::is_same_v<T, std::string>;
Is it possible to make a concept that checks for type equality of any type, so I could make templates looking something like this:
template<ValidType<int>... Ints> void passInts(Ints... ints) {}
template<ValidType<std::string>... Strings> void passStrings(Strings... strings) {}
That way I would only need to write a single concept checking for type equality. I know I could use conjunction for this but I think concepts are much cleaner.
I mean, you could write a concept to do that. Or you could just use std::same_as:
template<std::same_as<int>... Ints> void passInts(Ints... ints)
template<std::same_as<std::string>... Strings> void passStrings(Strings... strings)
When a concept is used against a template parameter pack, it is applied individually to each of the types in the pack.
Of course, this may not quite do what you need. The reason being that same_as is very serious about its name. If you want to invoke perfect forwarding through forwarding references, template argument deduction can start deducing the types as references to T. Which re not the "same_as" T.
So if you want to allow for perfect forwarding, you need a different concept: similar_to. Which sadly is not in the standard library, despite how simple and useful it is:
template<typename T1, typename T2>
concept similar_to = std::same_as<std::remove_cvref_t<T1>, T2>;
Note that this assumes that T2 has no reference or cv qualifiers. You can make a version that removes cvref from both.
Now, you can do this:
template<similar_to<int> ...Ints> void passInts(Ints && ...ints)
template<similar_to<std::string> ...Strings> void passStrings(Strings && ...strings)
And now you get perfect forwarding back.
Not sure I understand exactly what you want but you can add the type you want to check for as second template parameter:
template<typename T, typename U> concept ValidType = std::is_same_v<T, U>;
Then your two examples will work in that all template arguments will be restricted to int or std::string respectively.
In fact that is exactly what the std::same_as concept from the standard library already does. So just use that instead of ValidType to get additional benefits of constraint subsumption lacking in the simple variant above.
The syntax that works for classes does not work for concepts:
template <class Type>
concept C = requires(Type t) {
// ...
};
template <class Type>
concept C<Type*> = requires(Type t) {
// ...
};
MSVC says for the line of the "specialization": error C7606: 'C': concept cannot be explicitly instantiated, explicitly specialized or partially specialized.
Why cannot concepts be specialized? Is there a theoretical reason?
Because it would ruin constraint normalization and subsumption rules.
As it stands now, every concept has exactly and only one definition. As such, the relationships between concepts are known and fixed. Consider the following:
template<typename T>
concept A = atomic_constraint_a<T>;
template<typename T>
concept B = atomic_constraint_a<T> && atomic_constraint_b<T>;
By C++20's current rules, B subsumes A. This is because, after constraint normalization, B includes all of the atomic constraints of A.
If we allow specialization of concepts, then the relationship between B and A now depends on the arguments supplied to those concepts. B<T> might subsume A<T> for some Ts but not other Ts.
But that's not how we use concepts. If I'm trying to write a template that is "more constrained" than another template, the only way to do that is to use a known, well-defined set of concepts. And those definitions cannot depend on the parameters to those concepts.
The compiler ought to be able to compute whether one constrained template is more constrained than another without having any template arguments at all. This is important, as having one template be "more constrained" than another is a key feature of using concepts and constraints.
Ironically, allowing specialization for concepts would break (constrained) specialization for other templates. Or at the very least, it'd make it really hard to implement.
In addition to the great answer from Nicol Bolas:
Concepts are a bit special, because they don't behave like other templated things:
13.7.9 Concept definitions
(5) A concept is not instantiated ([temp.spec]).
[Note 1: A concept-id ([temp.names]) is evaluated as an expression. A concept cannot be explicitly instantiated ([temp.explicit]), explicitly specialized ([temp.expl.spec]), or partially specialized ([temp.spec.partial]). — end note]
Due to concepts not being able to be instantiated they also can't be specialized.
I'm not sure on why the standard decided to not make them specializable, given that it's easy to emulate specializations.
While you can't specialize concepts directly, there are quite a few ways you can work around the problem.
You can use any type of constant expression in a concept - so you could use a templated variable (which can be specialized) and just wrap it up into a concept - the standard does this for quite a few of its own concepts as well, e.g. std::is_intergral:
template<class T> struct is_integral;
// is_integral is specialized for integral types to have value == true
// and all others are value == false
template<class T>
inline constexpr bool is_integral_v = is_integral<T>::value;
template<class T>
concept integral = is_integral_v<T>;
So you could easily write a concept that has specializations like this: godbolt example
struct Foo{};
struct Bar{};
template<class T>
constexpr inline bool is_addable_v = requires(T t) {
{ t + t } -> std::same_as<T>;
};
// Specializations (could also use other requires clauses here)
template<>
constexpr inline bool is_addable_v<Foo> = true;
template<class T>
constexpr inline bool is_addable_v<T&&> = true;
template<class T>
concept is_addable = is_addable_v<T>;
int main() {
static_assert(is_addable<int>);
static_assert(is_addable<Foo>);
static_assert(!is_addable<Bar>);
static_assert(is_addable<Bar&&>);
}
Or by using a class:
template<class T>
struct is_addable_v : std::true_type {};
template<>
struct is_addable_v<struct FooBar> : std::false_type {};
template<class T>
concept is_addable = is_addable_v<T>::value;
Or even a constexpr lambda: godbolt example
// pointers must add to int
// everything else must add to double
template<class T>
concept is_special_addable = ([](){
if constexpr(std::is_pointer_v<T>)
return requires(std::remove_pointer_t<T> t) {
{ t + t } -> std::same_as<int>;
};
else
return requires(T t) {
{ t + t } -> std::same_as<double>;
};
})();
int main() {
static_assert(is_special_addable<double>);
static_assert(is_special_addable<int*>);
static_assert(!is_special_addable<double*>);
static_assert(!is_special_addable<int>);
}
So while concepts can't be specialized on their own, it's easy to achieve the same effect with existing language features.
Specialization in this sort of situation opens up a bag of worms. We opened this bag up once with template specialization. Template specialization is a major part of what makes the template language in general Turing complete. Yes, you can program in templates. You shouldn't, but you can. Boost has a library called Boost.MPL that's chock full of clever things, like an "unordered map" that operates at compile time, rather than run time.
So we would have to restrict it carefully. Simple cases may work, but complex cases would have to be forbidden. Certainly anything that is remotely capable of creating a recursive constraint would have to be watched carefully. Indeed, consider a concept:
template <typename T>
concept hailstone = false;
template <int i>
concept hailstone<std::integral_constant<int, i> =
hailstone<2 * i> || (i % 2 == 1 && hailstone<3*i - 1>);
template <>
concept hailstone<std::integral_constant<int, 0> = true;
so, is std::integral_constant<int, 27> a hailstone? It could take a while. My chosen example is based on hailstone numbers from the Collatz Conjecture. Determining whether any given number is a hailstone or not is painfully difficult (even though, as best as we can tell, every number is a hailstone number).
Now replace integral_constant with a clever structure which can do arbitrary precision. Now we're in trouble!
Now we can carefully slice off elements of this problem and mark them as doable. The spec community is not in that business. The Concepts we know in C++20 has been nicknamed concepts-lite because it's actually a drastically simplified version of a concepts library that never made it into C++11. That library effectively implemented a Description Logic, a class of logic that is known to be decidable. This was important because the computer had to run through all of the necessary calculations, and we didn't want them to take an infinite amount of time. Concepts is derived from this, so it follows the same rules. And, if you look in Description Logics, the way you prove many statements involves first enumerating the list of all named concepts. Once you had enumerated that, it was trivial to show that you could resolve any concept requirement in finite time.
As Nicol Bolas points out in his answer, the purpose of concepts was not to be some clever Turing complete system. It was to provide better error messages. Thus, while one might be able to cleverly slide in some specialization within carefully selected paths, there's no incentive to.
I would like to declare a function akin to the following:
string concat(const range<string> auto& strings);
I have achieved the same via the following:
template <template <typename> typename T> requires range<T<string>>
string concat(const T<string>& strings);
But this is too hefty and repetitious for me to consider utilizing.
Is there a cleaner way?
I assume there isn't, since a type concept requires the first template parameter to be a regular typename, which makes giving it a template argument list impossible.
If it is indeed impossible, are there any plans to remedy this apparent flaw? If not, are there any reasons for why this might prove troublesome to specify/implement?
Maybe something like this:
template <class R, class T>
concept range_of = std::ranges::range<R> &&
std::same_as<std::ranges::range_value_t<R>, T>;
static_assert(range_of<std::vector<int>, int>);
static_assert(range_of<decltype(std::declval<std::vector<int>&>() |
std::views::all),
int>);
Is there an already defined concept for specifying a range of a specific type, in C++20 or in the ranges-TS?
Something like:
template < class T, class InnerType >
concept RangeOf =
requires(T&& t) {
requires std::same_as<
std::remove_cvref_t<decltype(*std::ranges::begin(t))>,
InnerType
>;
std::ranges::end(t);
};
To allow, for example:
void print(const RangeOf<char> auto& char_seq) { /* ... */ }
No there isn't.
The way to spell that particular concept would be:
template <typename R, typename V>
concept RangeOf = range<R> && same_as<range_value_t<R>, V>;
But then it turns out there's a bunch of very closely related things you might want to check as well. You might actually want to look at the reference type rather than the value_type. Perhaps that's:
template <typename R, typename V>
concept RangeOf = range<R> && same_as<range_reference_t<R>, V>;
The reason is: do you consider vector<char>& to be a range of char or a range of char&? Arguably, it's more like the latter, and it's the latter that drives a lot more usage. But it's not like the former is wrong, it's useful too, just in a different way.
Then you have to talk about whether you want same_as or convertible_to. There are some algorithms in which you would want to restrict to the former, and some in which the latter to good enough. This, to me, is one of the motivating reasons for wanting concept template parameters, so that:
template <typename R, template <typename> concept C>
concept RangeOf = range<R> && C<range_value_t<R>>;
So that I can easily write RangeOf<same_as<char>> or RangeOf<convertible_to<char>>, depending on what I actually want.
All of which is to say, it's hard to know what actual thing we would want for RangeOf, which is why no such thing exists. But each specific thing that would be useful is very easy to write, so the lack of such a thing isn't a especially large burden on the user.
Is there any reason why the standard specifies them as template structs instead of simple boolean constexpr?
In an additional question that will probably be answered in a good answer to the main question, how would one do enable_if stuff with the non-struct versions?
One reason is that constexpr functions can't provide a nested type member, which is useful in some meta-programming situations.
To make it clear, I'm not talking only of transformation traits (like make_unsigned) that produce types and obviously can't be made constexpr functions. All type traits provide such a nested type member, even unary type traits and binary type traits. For example is_void<int>::type is false_type.
Of course, this could be worked around with std::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()>, but it wouldn't be as practical.
In any case, if you really want function-like syntax, that is already possible. You can just use the traits constructor and take advantage of the constexpr implicit conversion in integral_constant:
static_assert(std::is_void<void>(), "void is void; who would have thunk?");
For transformation traits you can use a template alias to obtain something close to that syntax:
template <bool Condition, typename T = void>
using enable_if = typename std::enable_if<Condition, T>::type;
// usage:
// template <typename T> enable_if<is_void<T>(), int> f();
//
// make_unsigned<T> x;
Note: this ends up looking more like a rant than a proper answer... I did got some itch reading the previous answers though, so please excuse me ;)
First, class traits are historically done with template structures because they predate constexpr and decltype. Without those two, it was a bit more work to use functions, though the various library implementations of is_base_of had to use functions internally to get the inheritance right.
What are the advantages of using functions ?
inheritance just works.
syntax can be more natural (typename ::type looks stupid TM)
a good number of traits are now obsolete
Actually, inheritance is probably the main point against class traits. It's annoying as hell that you need to specialize all your derived classes to do like momma. Very annoying. With functions you just inherit the traits, and can specialize if you want to.
What are the disadvantages ?
packaging! A struct trait may embed several types/constants at once.
Of course, one could argue that this is actually annoying: specializing iterator_traits, you just so often gratuitously inherit from std::iterator_traits just to get the default. Different functions would provide this just naturally.
Could it work ?
Well, in a word where everything would be constexpr based, except from enable_if (but then, it's not a trait), you would be going:
template <typename T>
typename enable_if<std::is_integral(T()) and
std::is_signed(T())>::type
Note: I did not use std::declval here because it requires an unevaluated context (ie, sizeof or decltype mostly). So one additional requirement (not immediately visible) is that T is default constructible.
If you really want, there is a hack:
#define VALUE_OF(Type_) decltype(std::declval<T>())
template <typename T>
typename enable_if<std::is_integral(VALUE_OF(T)) and
std::is_signed(VALUE_OF(T))>::type
And what if I need a type, not a constant ?
decltype(common_type(std::declval<T>(), std::declval<U>()))
I don't see a problem either (and yes, here I use declval). But... passing types has nothing to do with constexpr; constexpr functions are useful when they return values that you are interested in. Functions that return complex types can be used, of course, but they are not constexpr and you don't use the value of the type.
And what if I need to chain trais and types ?
Ironically, this is where functions shine :)
// class version
template <typename Container>
struct iterator { typedef typename Container::iterator type; };
template <typename Container>
struct iterator<Container const> {
typedef typename Container::const_iterator type;
};
template <typename Container>
struct pointer_type {
typedef typename iterator<Container>::type::pointer_type type;
};
template <typename Container>
typename pointer_type<Container>::type front(Container& c);
// Here, have a cookie and a glass of milk for reading so far, good boy!
// Don't worry, the worse is behind you.
// function version
template <typename Container>
auto front(Container& c) -> decltype(*begin(c));
What! Cheater! There is no trait defined!
Hum... actually, that's the point. With decltype, a good number of traits have just become redundant.
DRY!
Inheritance just works!
Take a basic class hierarchy:
struct Base {};
struct Derived: Base {};
struct Rederived: Derived {};
And define a trait:
// class version
template <typename T>
struct some_trait: std::false_type {};
template <>
struct some_trait<Base>: std::true_type {};
template <>
struct some_trait<Derived>: some_trait<Base> {}; // to inherit behavior
template <>
struct some_trait<Rederived>: some_trait<Derived> {};
Note: it is intended that the trait for Derived does not state directly true or false but instead take the behavior from its ancestor. This way if the ancestor changes stance, the whole hierarchy follows automatically. Most of the times since the base functionality is provided by the ancestor, it makes sense to follow its trait. Even more so for type traits.
// function version
constexpr bool some_trait(...) { return false; }
constexpr bool some_trait(Base const&) { return true; }
Note: The use of ellipsis is intentional, this is the catch-all overload. A template function would be a better match than the other overloads (no conversion required), whereas the ellipsis is always the worst match guaranteeing it picks up only those for which no other overload is suitable.
I suppose it's unnecessary to precise how more concise the latter approach is ? Not only do you get rid of the template <> clutter, you also get inheritance for free.
Can enable_if be implemented so ?
I don't think so, unfortunately, but as I already said: this is not a trait. And the std version works nicely with constexpr because it uses a bool argument, not a type :)
So Why ?
Well, the only technical reason is that a good portion of the code already relies on a number of traits that was historically provided as types (std::numeric_limit) so consistency would dictate it.
Furthermore it makes migration from boost::is_* just so easier!
I do, personally, think it is unfortunate. But I am probably much more eager to review the existing code I wrote than the average corporation.
One reason is that the type_traits proposal is older than the constexpr proposal.
Another one is that you are allowed to add specializations for your own types, if needed.
Probably because boost already had a version of type_traits that was implemented with templates.
And we all know how much people on the standards committee copy boost.
I would say the mainreason is that type_traits was already part of tr1 and was therefore basically guaranteed to end up in the standard in more or less the same form, so it predates constexpr. Other possible reasons are:
Having the traits as types allows for overloading functions on the type of the trait
Many traits (like remove_pointer) define a type instead of a value, so they have to be expressed in this way. Having different interfaces for traits defining values and traits defining types seems unnessecary
templated structs can be partial specialized, while functions can't, so that might make the implementation of some traits easier
For your second question: As enable_if defines a type (or not, if it is passed false) a nested typedef inside a struct is really the way to go