2

In the following program template function f calls overloaded template function p and one of the overloads is declared after f, but as far as I understand it must be found at the point of instantiation using argument-dependent lookup:

struct A{};

constexpr bool p(auto) { return false; }
constexpr bool f(auto v) { return p(v); }
constexpr bool g() { return f(A()); }
constexpr bool p(auto) requires true { return true; }

static_assert( f(A{}) );

The static_assert passes in MSVC and in GCC only with -O0 option, and it fails in Clang and in GCC with -O1 and higher optimization options. Online demo: https://godbolt.org/z/crvE5azWe

Is there any undefined behavior in the program, and if not, which compiler is correct here?

5
  • I heard a rumor that requires true is ill formed, no diagnostics required. If you replace true by something that depends on the argument type, such as sizeof(&arg), then the behavior is consistent. godbolt.org/z/f1GxP3Y3a Commented Feb 1, 2024 at 20:39
  • @alfC, thanks, but in your modification all compilers complain about invalid program syntax: error: expression must be enclosed in parentheses Commented Feb 2, 2024 at 7:32
  • Sorry for the confusion, that was not the point I wanted to make. godbolt.org/z/snG5nTdjc Commented Feb 2, 2024 at 8:09
  • @alfC, now GCC complains error: constraint expression does not have type 'bool' Commented Feb 2, 2024 at 8:12
  • 1
    You are all right, I wasn't paying attention. I think these are not solutions: godbolt.org/z/Gqa1z319P and behavior is still inconsistent. I deleted my answer. Commented Feb 2, 2024 at 8:17

1 Answer 1

3

The declarations of the p templates are IFNDR (ill-formed, no diagnostic required) per https://eel.is/c++draft/temp#over.link-7.sentence-2:

The two declarations have corresponding signatures (which doesn't consider trailing requires-clauses), but they do not correspond because that does require equivalent trailing requires-clauses. If they did correspond however, then the declarations would (re-)declare the same function template.

Also, the two function templates accept and satisfy the same template argument lists. requires true does not constrain the template in any form.

Two function template declaration can't be "functionally equivalent" but not "equivalent" (in which case they would declare the same function template). If the templates have the exact same signature and accept/satisfy the same template arguments, then they should be the same template and to make sure that they can be recognized as same template, their declarations should have the exact same form (except for template parameter names).


Even if you resolve that issue by making the second overload not functionally equivalent, you still have the following problem:

g is not a template, so the use of f(A()) in its definition already causes instantiation of the specialization f<A>. Since this is earlier than the instantiation of the same specialization that would be required from static_assert( f(A{}) ); the latter doesn't matter.

There are two points of instantiation for a function template specialization: Either directly after the declaration from which the instantiation is required, or at the end of the translation unit.

At the first point of instantiation the second p overload hasn't been declared yet. Therefore it can't be found by name lookup. At the second point of instantiation the second p has been declared and, as you are correctly arguing, should be found by ADL and used for the call.

So, the overload resolution gives different results depending on the point of instantiation. If two points of instantiation of a template specialization cause the meaning of the program to change, then the program is IFNDR as well.

In other words, you are not allowed to add an overload that would have been chosen by overload resolution via ADL after the point where the overload resolution is first required. All functions that should be connected to the class via ADL should be declared before any use of the class (typically in a header for the class).

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. I changed the second overload to constexpr bool p(A) { return true; } so the referenced item does not apply any more, but the result is the same: godbolt.org/z/nzYYehGon
@Fedor See edit for the second problem.
There is a similar issue fixed recently in Clang: github.com/llvm/llvm-project/issues/… Does the example from there also IFNDR (so the fix was not necessary)?
@Fedor The second problem mentioned in my answer doesn't apply there because the argument type is int, so that ADL isn't done and the second overload can't be considered at either point of instantiation. The first problem still applies and makes it in my opinion IFNDR. The relevant wording in the standard changed a lot though and the sentence I quote was added only with CWG 2603. I think it was IFNDR in the C++20 wording as well though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.