Why can't we specialize concepts? - c++

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.

Related

Why cannot concepts be passed to template meta-functions?

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;

Any good reason for the existance of helper variable template?

° Preamble
This question is particularly related to the helper variable templates defined by/in the STL for all the types deriving from std::integral_constant.
° Context
I am in the process of writing a compile-time only library which aims to provide the most possible features of the STL (until C++17 for now), by using the least possible features of the language after C++11.
That is, everything that can be done using C++11 features only, are implemented using C++11. For things that cannot be implemented like that, the library will have to provide other options...
Side Note
The purpose is to minimize the needed modifications to the code, produced using the library, when this code has to be compiled with compilers having a reduced set of language features. Ie: Compilers of the 'embedded world' does often not provide everything one would them to be able to do.
° Chronology
C++11 standard library came up with std::integral_constant.
This 'helper class' was already defining the cast operator for value_type.
C++14 added the invoke operator to it, and this 'new' language feature 'variable template'.
C++17 added std::bool_constant though std::true_type and std::false_type were already defined from C++11 as std::integral_constant<bool, true and false > respectively.
C++17 also added inline variable template... There, suddenly, all the types deriving from std::integral_constant were all defining a 'helper' variable template.
Note
I perfectly understand what is the purpose of an inline variable template.
The question here is about the usefulness of the 'helpers' defined for the types deriving from std::integral_constant.
° A Bit Food
Now, consider the following code examples:
/* Test template using std::integral_constant<bool, false>
*/
template<typename...>
using AlwaysFalse = std::false_type;
/* Example #1
*/
template<typename T>
struct AssertAlwaysFalse {
static_assert(
AlwaysFalse<T>{},
"Instatiation and bool cast operator replaces variable template."
);
using Type = T;
};
using AlwaysFalseType = typename AssertAlwaysFalse<int>::Type;
/* Example #2
*/
constexpr auto alwaysFalseAuto = AlwaysFalse<int>{};
constexpr bool alwaysFalseBool = AlwaysFalse<int>{};
/* Example #3
*/
template<bool AlwaysF>
struct AlwaysFalseArg { static constexpr bool Result = AlwaysF; };
constexpr bool alwaysFalseArg = AlwaysFalseArg<AlwaysFalse<int>{}>::Result;
The above examples show that instantiating an std::integral_constant, where a value is expected, has the exact same effect one would obtain by using a 'helper' variable template.
This is perfectly natural. std::integral_constant defines the cast operator for value_type. This behavior is purely C++11, and was available way before inline variable template.
° Still Stands The Question
Is there only one good reason for having defined these 'helper' variable templates for all the types deriving from std::integral_constant ???
° In Other Words
After the comment of #NicolBolas about:
"Why instantiating some object only to convert it into a compile-time value ?"
I realized that the main point behind the question was maybe not enough clear. So I will put it like that:
If you only had at disposal the features provided with C++11, How would you implement 'something' to provide this compile-time value ?
The main benefits are compilation speed, consistency, and convenience, primarily. I'm going to take a look at a few things here. I'll try to address both what the features are used for, and how one would implement them with only C++11 features. If you only care about the implementation ideas, skip to the bottom.
integral_constant itself:
First, we have the central object here, std::integral_constant. It defines a compile-time static constant, accessed as std::integral_constant<T, V>::value, and looks something like thistaken from cppreference.
template<class T, T v>
struct integral_constant {
static constexpr T value = v;
using value_type = T;
using type = integral_constant; // using injected-class-name
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; } // since c++14
};
Now, the first thing to note is that integral_constant stores the constant's value within itself, as a compile-time constant. You can access it without instantiating an instance; furthermore, instantiating the integral_constant will typically just result in an object being created and immediately converted to the constant, doing extra work for zero benefit; it's usually better to just use integral_constant::value directly instead.
constexpr bool TraitResult = integral_constant<bool, SomeConstexprTest(param)>::value;
SFINAE, Traits, and bool_constant:
The most common use case for integral_constant, by far, is as a compile-time boolean trait, likely used for SFINAE or introspection. The vast majority of <type_traits> consists of integral_constant<bool>s, with values determined according to the trait's logic, for use as yes-or-no tests.
template<typename T>
typename std::enable_if< std::is_same<SomeType, T>::value>::type
someFunc(T&& t);
template<typename T>
typename std::enable_if< !std::is_same<SomeType, T>::value>::type
someFunc(T&& t);
C++17 supplies bool_constant with this usage in mind, as a cleaner way to create boolean constants. It allows for cleaner code by simplifying the creation of custom traits:
namespace detail {
// These lines are clean.
template<typename...> struct are_unique_helper;
template<typename T> struct are_unique_helper<T> : std::true_type {};
// This... less so.
template<typename T, typename U, typename... Ts>
struct are_unique_helper<T, U, Ts...> : std::integral_constant<
bool,
!std::is_same<T, U> &&
are_unique_helper<T, Ts...>::value
> {};
}
// With integral_constant<bool>.
template<typename T, typename... Ts>
struct are_unique : std::integral_constant<bool, detail::are_unique_helper<T, Ts...>::value && are_unique<Ts...>::value> {};
// With bool_constant.
template<typename T, typename... Ts>
struct are_unique : std::bool_constant<detail::are_unique_helper<T, Ts...>::value && are_unique<Ts...>::value> {};
The name bool_constant conveys the same information as integral_constant<bool> with less wasted space or one less line, depending on coding style, and has the additional advantange of clearer conveyance thanks to emphasising the bool part. It's not strictly necessary, and can be easily supplied manually if your compiler doesn't support it, but it does provide a few benefits.
true_type and false_type:
These two provide specific constants for true and false; this is definitely useful, but many traits determine v with boolean logic. (See, e.g., std::is_same or are_unique above.) They're neither a be-all nor an end-all, though they can serve useful purposes such as base values for traits (as above, or as in std::is_same), or matching traits for overloading or SFINAE.
constexpr std::string_view isIntInner( std::true_type) { return "yes"; }
constexpr std::string_view isIntInner(std::false_type) { return " no"; }
template<typename T>
constexpr std::string_view isInt(T&&) {
return isIntInner(std::is_same<int, T>{});
}
Helpers: Type aliases & variable templates:
To explain the reason for the variable templates, we also want to look at them alongside the helper aliases defined in C++14.
template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;
template< class T, class U >
inline constexpr bool is_same_v = is_same<T, U>::value;
These mainly exist as a form of convenience, really; they're a bit faster to type, a bit cleaner to read, and require a bit less finger gymnastics. The type aliases were provided first, and the helper variables are mainly there for consistency with them.
How to implement these:
You mentioned that you're aiming to implement everything using C++11 features primarily. This will allow you to provide most of the above:
integral_constant: Requires only C++11 or earlier features. (constexpr and noexcept.)
bool_constant: Introduced in C++17, but requires only C++11 features. (Alias template.)
true_type and false_type: Same as integral_constant.
Introspective logic type traits: Same as integral_constant.
Helper _t aliases: Introduced in C++14, but requires only C++11 features. (Alias template.)
Helper _v variables: Requires C++14 features. (Variable template.)
It wouldn't actually be too hard to provide helper aliases and bool_constant for compilers which don't support it, as long as using templates is supported. Possibly by, e.g., providing them within your library's namespace, in a header which is only loaded on implementations that don't include the aliases, and/or which the library's consumer can enable or disable as necessary during compilation.
While it would take a lot of work to implement variable templates, however, you do have another, more C++11-compliant option: helper functions.
template<typename T, typename U>
inline constexpr bool is_same_v() noexcept {
return std::is_same<T, U>::value;
}
Providing functions of this sort will result in code nearly as clean as the helper variable templates, which can be cleanly switched over to the official variable templates for compilers which provide them. There are a few slight differences between helper functions and helper variables, though I'm not sure if there are any use cases that would actually care about them, and the library would ideally only provide helper functions for compilers which don't themselves provide the _v variables.

How can unspecified types be used in C++20 'requires' expressions?

I'm trying to write a C++20 concept to express the requirement that a type have a certain method, which takes an argument, but for the purposes of this concept I don't care what the argument type is.
I've tried to write something like:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
however, both gcc and clang reject this, giving an error that 'auto' cannot be used in the parameter list of a requires expression this way.
An alternative would be to put the type of 'x' as a second template parameter:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
but then this requires TX to be specified explicitly whenever the concept is used, it cannot be deduced:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
Is there any way to write a concept that allows Foo to take an argument of unspecified type?
The question Concept definition requiring a constrained template member function is very similar, but not the same: that question asks how to require that a (templated) method can take any type satisfying a given concept, while this question is about requiring that a method takes some particular type, although that type is unspecified. In terms of quantifiers, the other question is asking about (bounded) universal quantification while this one is about existential quantification. The other question's answer also does not apply to my case.
Concepts are not intended to provide the kind of functionality you are looking for. So they don't provide it.
A concept is meant to constrain templates, to specify a set of expressions or statements that a template intends to use (or at least be free to use) in its definition.
Within the template that you are so constraining, if you write the expression t.Foo(x), then you know the type of x. It is either a concrete type, a template parameter, or a name derived from a template parameter. Either way, the type of x is available at the template being constrained.
So if you want to constrain such a template, you use both the type of t and the type of x. Both are available to you at that time, so there is no problem with creating such a constraint. That is, the constraint is not on T as an isolated type; it's on the association between T and X.
Concepts aren't meant to work in a vacuum, devoid of any association with the actual place of usage of the constraint. You shouldn't focus on creating unary concepts so that users can static_assert their classes against them. Concepts aren't meant for testing if a type fulfills them (which is basically what your static_assert is doing); they're meant for constraining the template definition that uses them.
Your constraint needs to be FooCallableWith, not HasFooMethod.
Something close to this can be accomplished by defining an adapter type that can implicitly convert to (almost) anything:
struct anything
{
// having both these conversions allows Foo's argument to be either
// a value, an lvalue reference, or an rvalue reference
template <typename T>
operator T&();
template <typename T>
operator T&&();
};
Note that these operators do not need to be implemented, as they will only be used in an unevaluated context (and indeed, they could not be implemented for all types T).
Then, HasFooMethod can be written as:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};

Why are C++11 type traits not alias templates?

Similar question: Why are type_traits implemented with specialized template structs instead of constexpr? – but with a different answer.
I realise that alias templates cannot be specialised and hence can’t currently be used to implement type traits directly1. However, this is a conscious decision of the committee, and as far as I see there is no technical reason to forbid this.
So wouldn’t it have made more sense to implement type traits as alias templates, streamlining their syntax?
Consider
typename enable_if<is_pointer<T>::value, size_t>::type
address(T p);
versus
enable_if<is_pointer<T>, size_t> address(T p);
Of course, this introduces a breaking interface change when moving from Boost.TypeTraits – but is this really such a big problem?
After all, the code will need to be modified anyway since the types reside in different namespace and, as many modern C++ programmers are reluctant to open namespaces, will be qualified explicitly (if it would be changed at all).
On the other hand, it vastly simplifies the code. And given that template metaprogramming often gets deeply nested, convoluted and complex, it seems obvious that a clearer interface is beneficial.
Am I missing something? If not, I’d appreciate an answer that is not mere guesswork but relies on (and can cite) knowledge of the committee’s decision rationale.
1 But very well indirectly! Consider:
template <typename T> using is_pointer = typename meta::is_pointer<T>::type;
Where meta::is_pointer<T> corresponds to the current std::is_pointer<T> type.
The most concrete answer to your question is: No one ever proposed doing it that way.
The C++ standards committee is a multi-national, multi-corporation collection of volunteers. You're thinking of it as a design committee within a single organization. The C++ standards committee literally can not do anything without a proposal to put words into the draft standard.
I imagine that the reason there was no proposal is that type traits was an early proposal, with the boost implementation dating back to around 2000. And template aliases were late in getting implemented. Many of the committee members are reluctant to propose something that they have not implemented. And there was simply little opportunity to implement your proposal.
There was a lot of pressure to ship C++11. It really was intended to ship in 2009 and when that ship date slipped, it was very tough to do anything to the working paper besides fix the features already under consideration. At some point you've got to put great new ideas on the back burner, lest you will never ship.
Update
As of C++14, the TransformationTraits (those which result in a type) now have template alias spellings, for example:
template <bool b, class T = void>
using enable_if_t = typename enable_if<b,T>::type;
And the C++1z working draft now has template variable spellings for the traits resulting in values:
template <class T>
constexpr bool is_pointer_v = is_pointer<T>::value;
Also, even in C++11 one could do:
typename enable_if<is_pointer<T>{}, size_t>::type
address(T p);
I.e. you can use {} in place of ::value (assuming your compiler has constexpr support). In C++14 that becomes:
enable_if_t<is_pointer<T>{}, size_t>
address(T p);
And in C++1z:
enable_if_t<is_pointer_v<T>, size_t>
address(T p);
Note that the difference between C++1z and C++14 is so minimal that it doesn't even save characters, just changes {} to _v and changes where you put these two characters.
Type traits, like several other libraries including <memory> and <functional>, were inherited from C++ TR1. Although that was a less formal document, it was more formal than Boost, and compatibility is worthwhile.
Also, note that type traits are all derived from std::integral_constant<bool>, which does implement a constexpr conversion function to bool. So that at least saves the ::value parts, if you so choose.
As a complete side-note since there seems to be confusion on how aliases may or may not help a trait like std::is_pointer:
You can go the Boost.MPL route and decide that you'll use Boost.MPL-style integral constants, which means types
template<typename Cond, typename Then = void>
using enable_if = typename std::enable_if<Cond::value, Then>::type;
// usage:
template<
typename T
, typename = enable_if<std::is_pointer<T>>
>
size_t address(T p);
or you can decide to use values instead
template<bool Cond, typename Then>
using enable_if = typename std::enable_if<Cond, Then>::type;
// can use ::value
template<
typename T
, typename = enable_if<std::is_pointer<T>::value>>
>
size_t address(T p);
// or constexpr conversion operator
template<
typename T
, typename = enable_if<std::is_pointer<T> {}>
>
size_t address(T p);
Note that in the latter case it's not possible to use enable_if<std::is_pointer<T>()>: std::is_pointer<T>() is a function type (taking void and returning std::is_pointer<T>) and is invalid since our alias takes a value and not a type in this case. The braces ensure that it is a constant expression instead.
As you may have noticed, std::is_pointer doesn't benefit from template aliases at all. This isn't surprising at it's a trait where the interesting part is accessing ::value, not ::type: template aliases can only help with member types. The type member of std::is_pointer isn't interesting since it's an Boost.MPL-style integral constant (in this case either std::true_type or std::false_type), so this doesn't help us. Sorry!

Why are type_traits implemented with specialized template structs instead of constexpr?

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