Home > Net >  Requires clause positioning in C 20 function templates
Requires clause positioning in C 20 function templates

Time:05-17

In C 20, you can write a constrained function template in a couple of different ways:

template <typename T>
concept Fooable = true;

template <typename T>
    requires Fooable<T>
void do_something(T&); // (1)

template <typename T>
void do_something(T&) requires Fooable<T>; // (2)

According to the accepted answer in this question, these two forms are equivalent (which has always been my understanding).

However, I notice that GCC 12.1 considers (1) and (2) to be two different functions, rather than (2) being a redeclaration: it's possible to provide definitions for both, and attempting to call do_something() is then ambiguous (example).

  • Is GCC correct, and these are two different functions?
  • If so, are there any technical differences in meaning between the two styles?

EDIT:

  • As pointed out in the comments, the linked questions states that a function template declaration and definition must use the same "requires style". What is the reason for this restriction?

(I vaguely recall from the Concepts TS days that requirements underwent "normalisation" to decide when they were equivalent -- I guess this is no longer the case in C 20?)

CodePudding user response:

The wording in this area has moved around a bit. In C 20, we had this rule in [temp.over.link]/7:

  1. Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters. Two function templates are functionally equivalent if they are declared in the same scope, have the same name, accept and are satisfied by the same set of template argument lists, and have return types and parameter lists that are functionally equivalent using the rules described above to compare expressions involving template parameters. If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.

  2. [Note 7: This rule guarantees that equivalent declarations will be linked with one another, while not requiring implementations to use heroic efforts to guarantee that functionally equivalent declarations will be treated as distinct.

// guaranteed to be the same
template <int I> void f(A<I>, A<I 10>);
template <int I> void f(A<I>, A<I 10>);

// guaranteed to be different
template <int I> void f(A<I>, A<I 10>);
template <int I> void f(A<I>, A<I 11>);

// ill-formed, no diagnostic required
template <int I> void f(A<I>, A<I 10>);
template <int I> void f(A<I>, A<I 1 2 3 4>);

-end note]

In your example:

template <typename T>
    requires Fooable<T>
void do_something(T&); // (1)

template <typename T>
void do_something(T&) requires Fooable<T>; // (2)

These are functionally equivalent (they have the same constraints, basically) but not equivalent (they have different template-heads - the requires clause after the template parameters is part of the template-head), which makes this ill-formed no diagnostic required. In practice, because they're not equivalent, they're different overloads - but because they're functionally equivalent, any attempt to call would be ambiguous between them.

As I point out in the other answer, these have the same meaning - it's just that you have to stick with one form for the declaration and the definition if you split them.


The current wording, after Davis Herring's omnibus paper P1787, involves going up to [basic.scope.scope]/4:

Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless [...] each declares a function or function template, except when [...] both declare function templates with equivalent non-object-parameter-type-lists, return types (if any), template-heads, and trailing requires-clauses (if any), and, if both are non-static members, they have corresponding object parameters.

This makes the two do_somethings not correspond, which makes them different function templates. We don't run into the new functionally equivalent but not equivalent rule (so we're not ill-formed, no diagnostic required), but we just have two function templates that are necessarily ambiguous in all cases. So... not the most useful thing in the world.

  • Related