Functional equivalency in template constraints vs member function constraints
Asked Answered
D

1

18

The latest standard draft N4910 has this example in [temp.over.link] regarding functional equivalence:

template<int I> concept C = true;
template<typename T> struct A {
void f() requires C<42>; // #1
void f() requires true; // OK, different functions
};

My understanding is that this is ok, since C<42> and true are unevaluated operands. Therefore according to [temp.over.link]/5 when considering whether the constraints are functionally equivalent, not the result of the expressions, but what operations are performed on which entity and in what order, is deciding functional equivalency of the constraints.

However if the constraints were functionally equivalent, then because they are not equivalent, by [temp.over.link]/7 the program would be ill-formed, no diagnostic required, as declaring the same member twice would make the program ill-formed.

On the other hand

template<typename>
requires C<42>
void g() {};

template<typename>
requires true
void g() {};

seems to be ill-formed, no diagnostic required, because [temp.over.link]/6 says that template heads are functionally equivalent if they accept and satisfy the same template arguments.

Am I misunderstanding the example and referenced standard wording or is there really such a difference? If so, why?

Dees answered 29/3, 2022 at 18:39 Comment(0)
C
1

It's to avoid having to mangle constraints into function symbols (see: Why decltype expressions in return types have to be mangled in the symbol name?).

Consider the case where the two function templates are in different TUs, x.cpp and y.cpp:

// x.cpp
template<int> concept C = true;
template<typename> requires C<42> int g() { return 1; }
int x() {
    volatile auto p = g<int>;
    return p();
}

// y.cpp
template<typename> requires true int g() { return 2; };
int y() {
    volatile auto p = g<int>;
    return p();
}

If this were to be valid, we'd need to mangle constraints into function template instantiation symbols, but we really (really) don't want to do that, since it would be a huge pain for ABI compatibility (we'd never be able to tighten, relax or even refactor constraints) as well as a mild headache for implementors. But it can't be ODR ill-formed NDR, since to make this an ODR violation we'd have to determine that the two function templates are functionally equivalent, which is undecidable (Church). So we make equivalent-but-not-functionally-equivalent its own IFNDR.

However, this problem does not arise for class template member functions; a class cannot redeclare member functions, and must be completed within a TU, and class definitions across TUs must be token-level equivalent. So it's no problem to allow the two distinct member function declarations, since they can't be referred to anyway. Well... - https://github.com/cplusplus/CWG/issues/256

Cementite answered 8/3, 2023 at 20:4 Comment(2)
What if the template-heads are neither equivalent nor functionally equivalent, but accept overlapping sets of arguments? template<class> int f() and template<std::same_as<int>> int f(), for example, can both be instantiated with <int>, so the constraints need to be mangled to differentiate the two.Mcgrath
@Mcgrath uh, yes, this is a bit of a mess. See github.com/itanium-cxx-abi/cxx-abi/issues/24Cementite

© 2022 - 2024 — McMap. All rights reserved.