C++ partial concept id: What is the reason for the explicit template specification order / special status of first argument? - c++

I started experimenting with the C++20 feature of concepts and was very pleased when I realized that it is possible to partially explicitly provide template arguments for concepts. I read the cppreference article and did not find that mentioned there.
But then I realized something strange: the order of specification of template arguments is reversed to what I would have expected. When providing one explicite template argument, it replaces the second template in the template list:
#include <concepts>
#include <type_traits>
/// Concept in order to deduce if sth. is base of sth else
template <typename Impl, typename Base> //XXX: here the order of Impl and Base are not
concept Implements = std::is_base_of_v<std::remove_reference_t<Base>, // what I would've expected.
std::remove_reference_t<Impl>>;
/// Example Base class impl
struct BaseExample {};
/// Implementation of BaseExample
struct ImplExample : BaseExample {};
/// Function in which the concept is applied
template <Implements<BaseExample>... Baes> void f(Baes &&... ) {}//} ((void)b, ...); }
int main() {
(void) std::is_base_of_v<BaseExample, std::remove_reference_t<ImplExample &&>>; //< true
(void) std::is_base_of_v<BaseExample, std::remove_reference_t<ImplExample&>>; //< true
f(ImplExample{}, ImplExample{});
}
From my point of view the possibility to partially provide explicit template arguments makes sense, as the argument against partial template specification for classes do not apply here and make concepts more general. Now I wonder:
Will partial explicit template specifications (likely) be allowed when the standard is released?
Will this order of specifications likely stay the same or is this a bug?
How would I answer this question for myself? From what I understand the c++20 standard is not ready by now and I found a list of C++ Standard Committee Papers, of which I briefly searched the headlines of the ones proposed in 2020 for 'concept'. Is checking these papers the way to go, or is there an accessible single document which combines the points the authors currently agreed upon?
The code can be found here.
edit
After posting this I checked the behavior when three template arguments are specified. It looks like I misinterpreted the specification order: The first argument is 'held free' to contain the argument to be checked, and the explicit specifications start with the second argument. This can be seen here.
Even though I figured out the reasoning behind the order of specification I would be very interested in the answers to questions above.

Yes, partial-concept-ids are surely a C++20 thing. The special status of the first argument, while surprising, allows cases like std::constructible_from which is declared as
template<class T,class ...Args>
concept constructible_from=…;
std::constructible_from<int,int> is a type-constraint that requires that whatever it introduces be constructible from two int arguments. However, it can also be an expression, in which case it reports whether an int can be constructed from an int (spoilers: true), but that potential confusion exists regardless of the argument order.
If T had to go at the end, there would be no way of using such a concept: only template argument deduction or default template arguments can supply values for a template parameter beyond a parameter pack, and neither of those applies here.
Every mailing posted at the papers site you linked includes the latest draft of the standard, and alternate mailings include annotations as to what papers were adopted. Or you can just visit the draft’s repository (at least if you’re happy reading LaTeX).

Related

C++20: Validate Template Bodies Against Concepts

C++20 introduces concepts, which allows us to specify in the declaration of a template that the template parameters must provide certain capabilities. If a template is instantiated with a type that does not satisfy the constraints, compilation will fail at instantiation instead of while compiling the template's body and noticing an invalid expression after substitution.
This is great, but it begs the question: is there a way to have the compiler look at the template body, before instantiation (i.e. looking at it as a template and not a particular instantiation of a template), and check that all the expressions involving template parameters are guaranteed by the constraints to exist?
Example:
template<typename T>
concept Fooer = requires(T t)
{
{ t.foo() };
};
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
}
The concept prevents me from instantiating callFoo with a type that doesn't support the expression that's inside the template body. However, if I change the function to this:
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
fooer.bar();
}
This will fail if I instantiate callFoo with a type that defines foo (and therefore satisfies the constraints) but not bar. In principal, the concept should enable the compiler to look at this template and reject it before instantiation because it includes the expression fooer.bar(), which is not guaranteed by the constraint to exist.
I assume there's probably backward compatibility issues with doing this, although if this validation is only done with parameters that are constrained (not just typename/class/etc. parameters), it should only affect new code.
This could be very useful because the resulting errors could be used to guide the design of constraints. Write the template implementation, compile (with no instantiations yet), then on each error, add whatever requirement is needed to the constraint. Or, in the opposite direction, when hitting an error, adjust the implementation to use only what the constraints provide.
Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?
Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?
No, no, and no.
The feature you're looking for is called definition checking. That is, the compiler checks the definition of the template at the point of its definition based on the provided concepts, and issues errors if anything doesn't validate. This is how, for instance, Rust Traits, Swift Protocols, and Haskell Typeclasses work.
But C++ concepts don't work like that, and it seems completely infeasible to ever add support for such a thing given that C++ concepts can be arbitrary expressions rather than function signatures (as they are in other languages).
The best you can do is thoroughly unit test your templates with aggressively exotic types that meet your requirements as minimally as possible (the term here is archetype) and hope for the best.
TL;DR: no.
The design for the original C++11 concepts included validation. But when that was abandoned, the new version was designed to be much more narrow in scope. The new design was originally built on constexpr boolean conditions. The eventual requires expression was added to make these boolean checks easier to write and to bring some sanity to relationships between concepts.
But the fundamentals of the design of C++20 concepts makes it basically impossible to do full validation. Even if a concept is built entirely out of atomic requires expressions, there isn't a way to really tell if an expression is being used exactly in the code the way it is in the requires expression.
For example, consider this concept:
template<typename T, typename U>
concept func_to_u = requires(T const t)
{
{t.func()} -> std::convertible_to<U>;
};
Now, let's imagine the following template:
template<typename T, typename U> requires func_to_u<T, U>
void foo(T const &t)
{
std::optional<U> u(std::in_place, t.func());
}
If you look at std::optional, you find that the in_place_t constructor doesn't take a U. So... is this a legitimate use of that concept? After all, the concept says that code guarded by this concept will call func() and will convert the result to a U. But this template does not do this.
It instead takes the return type, instantiates a template that is not guarded by func_to_u, and that template does whatever it wants. Now, it turns out that this template does perform a conversion operation to U.
So on the one hand, it's clear that our code does conform to the intent of func_to_u. But that is only because it happened to pass the result to some other function that conformed to the func_to_u concept. But that template had no idea it was subject to the limitations of convertible_to<U>.
So... how is the compiler supposed to detect whether this is OK? The trigger condition for failure would be somewhere in optional's constructor. But that constructor is not subject to the concept; it's our outer code that is subject to the concept. So the compiler would basically have to unwind every template your code uses and apply the concept to it. Only it wouldn't even be applying the whole concept; it would just be applying the convertible_to<U> part.
The complexity of doing that quickly spirals out of control.

Is there a reason why numeric_limits do not work on reference types?

If you mistakenly do something like:
#include<limits>
int arr[3];
auto x = std::numeric_limits<decltype(arr[0])>::max();
You will get unhelpful error message from the file in the STL implementation.
Problem is that template argument is a reference, so the fix is to remove it:
auto x = std::numeric_limits<std::remove_reference_t<decltype(arr[0])>>::max();
Now my question is why numeric_limits do not know to do this by themselves?
I would understand that you do not want to remove pointerness(since max of char pointer and max of char are very very different things), but I would assume that whenever you have a reference as an argument to numeric_limits you would be happy with result that is obtained by removing it.
From a technical point of view, there is no reason why std::numeric_limits<T> couldn't work with references. All what would be needed it to add a partial specialisations like this:
namespace std {
template <typename T> struct numeric_limits<T&>: numeric_limits<T> {};
template <typename T> struct numeric_limits<T&&>: numeric_limits<T> {};
template <typename T> struct numeric_limits<T const>: numeric_limits<T> {};
template <typename T> struct numeric_limits<T volatile>: numeric_limits<T> {};
template <typename T> struct numeric_limits<T const volatile>: numeric_limits<T> {};
}
A user can't add these specialisations, of course. However, that's not a huge constraint as a custom variant of numeric_limits can be created in a suitable namespace.
As it is technically doable the question now becomes why the standard doesn't provide these declarations. I don't think there will be a conclusive answer (unless this idea was discussed and discarded with a suitable and still accessible record). Here are some of the potential answers:
The feature wasn't proposed. When std::numeric_limits was introduced it specifically targeted replacing the macros in <limits.h> with a more a C++ approach. Something like decltype(expr) and forwarding references didn't exist, i.e., template arguments wouldn't be "accidentally" deduced as reference types. Thus, removing qualifiers wasn't a concern at the time.
I'm not sure if at the point in history when numeric_limits were added partial template specialisation already existed. Even if it existed, anything resembling template meta programming didn't exist. As a result, it may not have been possible or assumed to be possible to meddle with the template argument type in the necessary way.
Even if it were considered, I doubt the committee would have gone with adding the partial specialisations: numeric_limits<T> inspects the traits of type T but reference types don't have a max() or digits. Also, if reference types are supported because "clearly" the desired property must be the one of the underlying type where to stop: should std::numeric_limits<int*>::max() provide the same value as std::numeric_limits<int>::max(), too? After all, it also doesn't make any sense on pointers.
Considering that the original proposal almost certainly didn't cover the case of qualified types (see above), another reason why the feature isn't available is that it simply wasn't proposed: without a proposal the standard won't get changed. Whether the standard would get changed if the feature were proposed is a separate question. There is a proposal in this general space (P0437r0) but browsing over it I don't think this proposal covers qualified types, either.

C++11 static_assert (and functions to be used therein)

static_assert seems to be a very nice feature together with templates.
However, I have trouble finding functions in the standard library for doing various tests at compile time.
For example, I am looking for a function to check whether a type is a subtype of another one. boost::is_base_of does the job, however, is a comparable function in std, so I do not need to rely on boost.
Basically, is there a good source for a list of functions which can be used in static_assert and are contained in the standard library of C++11?
When is static_assert executed? Can I put it anywhere in a template and it is evaluated for each template instanciation? Could it be used to constrain template parameters to be a specific subtype of a class?
Take a look at the final C++11 draft, section 20.7, particularly the <type_traits> header.
What you are asking is: std::is_base_of<base, derived>::value;
Regarding your question: static_assert can be evaluated whenever the compiler sees fit, but it will usually:
In a template: if the expression uses dependent names, in instatiation time; else, in definition time.
Out of template: in definition time.
In addition to #rodrigo’s answer (he was faster …),
When is static assert executed? Can I put it anywhere in a template and it is evaluated for each template instanciation? Could it be used to constrain template parameters to be a specific subtype of a class?
Unfortunately, no. For instance, a static_assert(false, "bummer"); is always executed, no matter the template. This in particular fails if you want to (partially) specialise a template.
The standard (§7.4) says:
[If the condition to static_assert is false] the program is ill-formed, and the resulting diagnostic message (1.4) shall include the text of the string-literal, […]
Which is unfortunately quite unspecific but this lack of specificity is in fact exactly how static_assert behaves when it’s not dependent on a template type.
You need to make the condition in a static_assert depend on the template argument to bind its execution to the particular template argument.
So the following would fail:
template <typename T>
struct some_type {
static_assert(false, "T must be a pointer type");
};
template <typename T>
struct some_type<T*> {
// …
};
Finally, I heartily recommend you read Marthino’s article on More type traits which details this process more, and gives hints on how to solve many trait-related problems elegantly.

Can I rely on template type?

I am looking into working code from Game AI by example book and there is a part I do not understand.
There is
template <class node_type, class edge_type>
class SparseGraph
{ ... };
and
int SparseGraph<node_type, edge_type>::AddNode(node_type node)
{
if (node.Index() < (int)m_Nodes.size())
...
}
How can be node.Index()called?
There also is class
class GraphNode
{
public:
...
int Index()const{return m_iIndex;}
....
};
and graph is created with this class
typedef SparseGraph<GraphNode, GraphEdge> NavGraph;
NavGraph * m_pGraph;
so I understand what node.Index() does, BUT
how can I call node.Index() while there is no guarantee that node_type is GraphNode.
what if node_type is not GraphNode??
Hope you understand my question.
If node_type is not GraphNode, then your compiler will smack you and throw an error. However, if your class depends on the Index function, then you should document it as a requirement and any replacement for GraphNode must provide it, probably with some expected semantics.
Duck typing.
There is also a convenient feature in C++ called SFINAE (Substitution Failure Is Not An Error) that will remove a template from the considered candidates if an expression dependent on the type would not compile with the particular concrete type.
A way of restricting the types accepted as template parameters based on supported operations, i.e. Concepts, was originally planned for C++0x but scrapped due to committee disagreement on its design. You can still find early implementations in some forks of GCC and Boost also has a concepts library. See here.
C++ template functions are instantiated at point of use. i.e., it will paste in the types you specify when you specify them, and not sooner. At that point, if the type you specify does not have an Index member function, the compilation will fail.

Standard Library Containers with additional optional template parameters?

Having read the claim multiple times in articles - I want to add this question to Stackoverflow, and ask the community - is the following code portable?
template<template<typename T, typename Alloc> class C>
void f() {
/* some code goes here ... */
}
int main() {
f<std::vector>();
}
Is the implementation that supplies std::vector really allowed to have additional, defaulted template parameters beyond the two well known ones? This would render the above code ill-formed, as it assumes two template parameters. See the last paragraph in this article for an example of such a claim.
I found the following issue report, which says
There is no ambiguity; the standard is clear as written. Library implementors are not permitted to add template parameters to standard library classes. This does not fall under the "as if" rule, so it would be permitted only if the standard gave explicit license for implementors to do this. This would require a change in the standard.
The LWG decided against making this change, because it would break user code involving template template parameters or specializations of standard library class templates.
The books and people that say an implementation may add other optional parameters seem to be wrong.
Incredibly, I was recently reading "C++ Templates: The Complete Guide," and last book marked the following on page 111:
A template template argument must be a class template with parameters that exactly match the parameters of the template template parameter it substitutes. Default template arguments of a template template argument are ignored (but if the template template parameter has default arguments, they are considered during the instantiation of the template).
So, if the book is to be believe, your example where non-standard default parameters are added to std::vector would be legal - since default template arguments of a template template argument are ignored.
As a real world test, I compiled the following in g++ (successfully) and Visual Studio 2008 (failed on the mismatched parameters):
template<typename T1, typename T2, typename T3 = float>
class MyClass
{
public:
T1 v1;
T2 v2;
T3 v3;
};
template<template<typename T1, typename T2> class C>
void f()
{
C<int,double> *c = new C<int,double>();
}
int main ()
{
f<MyClass>();
return 0;
}
Check subsubsections of 17.4.4 [lib.conforming].
17.4.4.3/3 says that "a global or non-member function cannot be declared by the implementation as taking additional default arguments", but 17.4.4.4/2 explicitly allows replacing described member function signatures with longer ones so long as the additional parameters have defaults.
There's no section for templates, though, so if they felt the need to provide 17.4.4.3/3, it seems to me like extra template parameters are allowable barring wording to the contrary.
I have seen this claim, too. But.
For one, I have never seen an implementation doing this. I seem to remember that Andrei Alexandrescu once contemplated using things like allocator types on steroids (something like my_fancy_thing<std::allocator,more_info_to_pass_to_the_container>, while just std::allocator would still work, too). But even this would still keep your f() working, and that's the closest thing to an implementation breaking your example I have ever heard being discussed.
I think this falls pretty much into the same category as the claim that a 0 pointer does not necessarily have to be represented by a value with all bits set to zero - even if vendors really have that freedom (which I don't know, since there are claims from both sides, too), they won't ever use it, because that would break basically all existing code.
So I have long since decided to not to worry about it.