28

The following code:

#include <type_traits>

struct X {
    static constexpr void x() {}
};

template <class T1, class T2>
constexpr bool makeFalse() { return false; }

template <class T>
void foo() {
    T tmp;
    auto f = [](auto type) {
        if constexpr (makeFalse<T, decltype(type)>()) {
            T::x(); // <- clang does not discard
        } else {
            // noop
        }
    };
}

int main() {
    foo<int>();
}

does not compile with Clang, but compiles with GCC. I can't see anything wrong with this code, but I'm not sure. Is Clang right not compiling it?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nicolai
  • 1,140
  • 9
  • 17
  • worth mentioning that `T` is not dependant on the lambda template parameter. Don't know however how `if constexpr` should handle that. – bolov Apr 29 '19 at 19:00
  • (somewhat) equivalent example without lambda compiles fine , so I suspect it's a clang bug https://godbolt.org/z/Xok1wC – bolov Apr 29 '19 at 19:03
  • 1
    @bolov if you remove the generic lambda, it compiles too: https://godbolt.org/z/xoTBT6 – Amadeus Apr 29 '19 at 19:08

1 Answers1

19

[stmt.if]/2:

During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

Since makeFalse<T, decltype(type)>() is value-dependent after the instantiation of foo<int>, it appears that T::x() should be instantiated per the standard, and since T::x is ill-formed when T is int, Clang is right not compiling it.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • Wouldn't this reasoning imply that a hypothetical `if constexpr (makeFalse) { type.x(); }` would not be discarded either? – Barry Apr 29 '19 at 20:36
  • @Barry Yes. But `type.x()` is a dependent and possibly valid expression after the instantiation. – cpplearner Apr 29 '19 at 20:52
  • I think "possibly valid" is muddling things, I don't think that's relevant necessarily. Are you saying it won't be discarded even if `makeFalse` is `false`? Assume it's actually an interesting check... more like `if constexpr (can_x) { type.x(); }` – Barry Apr 29 '19 at 20:56
  • I am not convinced. Why then are my example and Amadeus compiling? – bolov Apr 29 '19 at 21:03
  • 5
    @Barry Let me try to clarify. There are two instantiations that can be involved: (1) the instantiation of `foo` (2) the instantiation of `f`'s function call operator template (which does not happen in OP's example). (1) does not discard either branch, because the condition is still value-dependent after it. If either branch is ill-formed after (1), an error will occur (but [\[temp.res\]/8](http://eel.is/c++draft/temp.res#8) may kick in when an expression in a branch is dependent). (2) _does_ discard one branch because the condition is no longer dependent. – cpplearner Apr 29 '19 at 21:09
  • @cpplearner Ah, perfect, there we go, that's what I was missing. Do you mind editing that into the answer? Effectively, clang is right to reject due to [temp.res]/8 but gcc is equally fine to accept because it's ill-formed ndr. – Barry Apr 29 '19 at 21:13
  • 6
    This is equivalent to the motivating example of [P0588R0](https://wg21.link/P0588R0), except that there's not even an implicit capture that could complicate things under the old rules. – T.C. Apr 30 '19 at 02:17