8

This works and outputs "1", because the function's constraints are partially ordered and the most constrained overload wins:

template<class T>
struct B {
    int f() requires std::same_as<T, int> {
        return 0;
    }
    int f() requires (std::same_as<T, int> && !std::same_as<T, char>) {
        return 1;
    }
};

int main() {
    std::cout << B<int>{}.f();
}

This works as well, because explicit instantiation doesn't instantiate member functions with unsatisfied constraints:

template<class T>
struct B {
    int f() requires std::same_as<T, int> {
        return 0;
    }
    int f() requires (!std::same_as<T, int>) {
        return 1;
    }
};

template struct B<int>;

So what should this do?

template<class T>
struct B {
    int f() requires std::same_as<T, int> {
        return 0;
    }
    int f() requires (std::same_as<T, int> && !std::same_as<T, char>) {
        return 1;
    }
};

template struct B<int>;

Currently this crashes trunk gcc because it compiles two functions with the same mangling. I think it would make sense to compile only the second function, so that the behavior is consistent with the regular overload resolution. Does anything in the standard handle this edge case?

unddoch
  • 5,790
  • 1
  • 24
  • 37

1 Answers1

5

C++20 recognizes that there can be different spellings of the same effective requirements. So the standard defines two concepts: "equivalent" and "functionally equivalent".

True "equivalence" is based on satisfying the ODR (one-definition rule):

Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.

There's more to it, but that's not an issue here.

Equivalence for template heads includes that all constraint expressions are equivalent (template headers include constraints).

Functional equivalence is (usually) about the results of expressions being equal. For template heads, two template heads that are not ODR equivalent can be functionally equivalent:

Two template-heads are functionally equivalent if they accept and are satisfied by ([temp.constr.constr]) the same set of template argument lists.

That's based in part on the validity of the constraint expressions.

Your two template heads in versions 1 and 3 are not ODR equivalent, but they are functionally equivalent, as they both accept the same template parameters. And the behavior of that code will be different from its behavior if they were ODR equivalent. Therefore, this passage kicks in:

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.

As such, all of the compilers are equally right because your code is wrong. Obviously a compiler shouldn't straight-up crash (and that should be submitted as a bug), but "ill-formed, no diagnostic required" often carries with it unforeseen consequences.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I guess ODR is One Definition Rule, but what is NDR then? – K.R.Park Feb 21 '22 at 01:01
  • @K.R.Park: "No diagnostic required". Normally if code is ill-formed, the compiler has to tell you about it. "Ill-formed, no diagnostic required" means that it doesn't have to tell you. NDR is employed on ODR violations, but also a few other rules where compiler detection is either flat-out impossible or cost-prohibitive. – Nicol Bolas Feb 21 '22 at 01:07
  • The second quote applies only to potentially-evaluated expressions. However a constraint-expression is an unevalutated operand. The sentence after the quote is concerned with unevaluated operands and seems much less strict to me. I don't think it would result in functional equivalency here. See also [this example](https://www.eel.is/c++draft/temp#over.link-example-4). – user17732522 Mar 29 '22 at 18:02
  • @user17732522: I corrected the quote, but I found that the template header rules basically amounts to what I originally said. – Nicol Bolas Mar 29 '22 at 18:45
  • @NicolBolas But which template-head's are violating the requirement here? I think it is referring to the grammar term. Anyway, just before you answered I had posted this as a question on its own: https://stackoverflow.com/questions/71667229/functional-equivalency-in-template-constraints-vs-member-function-constraints – user17732522 Mar 29 '22 at 18:48