Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalence between function templates and abbreviated function templates

All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.


[dcl.fct]/18 states that [extract, emphasis mine]:

An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]

Such that the following to function declarations are areuably equivalent:

template <typename T>
void f(T);

void f(auto);  // re-declaration

We may note, however, that the example of [dcl.fct]/18 states that

[...]

These declarations are functionally equivalent (but not equivalent) to the following declarations.

[...]

which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.

Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):

// A1: Clang OK, GCC error
template <typename T>
void a(T);

void a(auto) {}

// B1: Clang OK, GCC error
void b(auto);

template <typename T>
void b(T) {}

// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);

void c(auto, auto) {}

// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);

template <typename T>
void d(T, auto) {}

// E1: Clang error, GCC error
template <typename T>
void e(T, auto);

template <typename T>
void e(auto, T) {}

int main() {
    a(0);      // Clang OK, GCC error.
    b(0);      // Clang OK, GCC error.
    c(0, '0'); // Clang OK, GCC error.
    d(0, '0'); // Clang OK, GCC error.
    e(0, '0'); // Clang error, GCC error.
}

Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:

// A2: OK
struct Sa {
    template <typename T>
    void a(T);
};

void Sa::a(auto) {}

// B2: OK
struct Sb {
    void b(auto);
};

template <typename T>
void Sb::b(T) {}

// C2: OK
struct Sc {
    template <typename T, typename U>
    void c(T, U);
};

void Sc::c(auto, auto) {}

// D2: OK
struct Sd {
    template <typename T, typename U>
    void d(T, U);
};

template <typename T>
void Sd::d(T, auto) {}

// E2: Error
struct Se {
   template <typename T>
   void e(T, auto);
};

template <typename T>
void Se::e(auto, T) {}

with the following error messages:

GCC

error: no declaration matches 'void Se::e(auto:7, T)'

note: candidate is: 
  'template<class T, class auto:6> void Se::e(T, auto:6)'

Clang

error: out-of-line definition of 'e' does not match 
any declaration in 'Se'

Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.

GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.

Question:

  • Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?
like image 247
dfrib Avatar asked Aug 21 '20 14:08

dfrib


1 Answers

This:

template <typename T>
void e(T, auto);

Translates to this:

template<typename T, typename U>
void e(T, U);

By contrast, this:

template <typename T>
void e(auto, T) {}

translates to:

template <typename T, typename U>
void e(U, T) {}

Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.

You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.


As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.

What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".

So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.


While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.

P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.

That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.

However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.

Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.

So it's very possible that this is a mistake in the normative text.

like image 145
Nicol Bolas Avatar answered Oct 22 '22 17:10

Nicol Bolas