Skip to content

Incorrect if constexpr evaluation in nested generic lambda #58872

Open
@cschreib

Description

@cschreib

The test code below does not compile (in C++20 mode) with any version of clang I could test in Compiler Explorer (up to and including clang 15):

#include <type_traits>

// Generic type holder, for types that cannot be instanciated.
template<typename T>
struct type_holder {};

// Generic type to create a function with a given signature.
template<typename T>
struct foo;

template<typename R, typename ... Args>
struct foo<R(Args...)> {
    static R bar(Args...) {}
};

// Offending code.
template<typename T, typename ... IArgs>
auto test(IArgs... inputs) {
    [&]<typename R, typename... Args>(type_holder<R(Args...)>, Args... values) {
        // This always works.
        foo<T>::bar(values...);

        // This does not always work.
        if constexpr (std::is_same_v<R, void>) {
            if constexpr (sizeof...(Args) > 0) {
                foo<T>::bar(values...);
            } else {
                foo<T>::bar();
            }
        } else {
            int return_value = 0;
            if constexpr (sizeof...(Args) > 0) {
                return_value = foo<T>::bar(values...);
            } else {
                return_value = foo<T>::bar();
            }
        }
    }(type_holder<T>{}, inputs...);
}

int main() {
    // <source>:35:32: error: assigning to 'int' from incompatible type 'void'
    //                 return_value = foo<T>::bar();
    //                                ^~~~~~~~~~~~~
    test<void()>();

    //<source>:28:29: error: too few arguments to function call, expected 1, have 0
    //                foo<T>::bar();
    //                ~~~~~~~~~~~ ^
    test<void(int)>(1);

    // works!
    test<int()>();

    //<source>:28:29: error: too few arguments to function call, expected 1, have 0
    //                foo<T>::bar();
    //                ~~~~~~~~~~~ ^
    test<int(int)>(1);

    return 0;
}

Test cases:

  1. As you can see from the errors reported, test<void()>() generates an error inside an if constexpr branch that should not be entered. std::is_same_v<R, void> should be true, but it took (or at least, also tried to compile) the false branch.
  2. Similar story with test<void(int)>(1). There it took the correct branch for the return type, but then took the wrong branch for the arguments. sizeof...(Args) > 0 should be true, but it took (or at least, also tried to compile) the false branch.
  3. test<int()>() works fine, for some reason (it is the case that corresponds to the else branch for all if constexpr checks).
  4. test<int(int)>(1) fails again and takes the wrong branch for the return value and the arguments.

Extra information:

  • The code compiles if using foo<R(Args...)> instead of foo<T>, which should be equivalent. Weirdly enough, as this seems orthogonal to the if constexpr issue.
  • The code always failed to compile with clang since generic lambda with explicit template parameters were added (clang 9 with -std=c++2a).
  • The code compiles with GCC 8 (-std=c++2a) and above.
  • The code compiles with MSVC 19.30 and above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang:frontendLanguage frontend issues, e.g. anything involving "Sema"constexprAnything related to constant evaluationlambdaC++11 lambda expressions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions