Search code examples
c++if-constexpr

if constexpr not-taken branch doesn't need to be valid, sure?


I'm testing the code below:

#define TO_STRING(X) #X

#define TEST(X, Y) namespace Y { constexpr std::string_view name{#X}, raw_value{TO_STRING(X)};\
constexpr bool defined {name!=raw_value}; constexpr bool has_value{raw_value!=""}; }

#define A
#define B 1

TEST(A, a);
TEST(B, b);
TEST(C, c);

int main()
{
    if constexpr (a::defined && a::has_value) {
        std::cout << a::name << " = " << a::raw_value << '\n';
    } else {
        std::cout << a::name << " not defined or have no value\n";
    }
    if constexpr (b::defined && b::has_value) {
        std::cout << b::name << " = " << b::raw_value << '\n';
    } else {
        std::cout << b::name << " not defined or have no value\n";
    }
    if constexpr (c::defined && c::has_value) {
        std::cout << c::name << " = " << c::raw_value << '\n';
    } else {
        std::cout << c::name << " not defined or have no value\n";
    }
    return 0;
}

Which produces the following output:

A not defined or have no value
B = 1
C not defined or have no value

If I modify the TEST adding a new function:

#define TEST(X, Y) namespace Y { constexpr std::string_view name{#X}, raw_value{TO_STRING(X)};\
constexpr bool defined {name!=raw_value}; constexpr bool has_value{raw_value!=""};\
constexpr auto value() { if constexpr (defined && has_value) return X; else return; } }

I was expecting the compiler to ignore the return X statement while expanding TEST(C, c) macro, it reports an error instead:

error: use of undeclared identifier 'C'
TEST(C, c);
     ^

Referencing an undeclared identifier is absolutely ill formed but I was expecting the compiler to ignore it because I thought that if constexpr untaken branches didn't need to be well formed. Is there a way to achieve this behaviour?


Solution

  • [stmt.if]/2

    If the value of the ... condition is false, the first substatement [aka the first branch] is a discarded statement, otherwise the second substatement, if present, is a discarded statement. 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.

    (bold mine)

    This explains the behavior of if constexpr inside of templates. Since nothing else is said about it, its behavior outside of templates matches the behavior of the plain if.

    Now, if you're thinking about making your function a template by adding an unused template parameter, it's not going to work so easily:

    [temp.res.general]/6.1

    The program is ill-formed, no diagnostic required, if:

    — no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated

    (bold mine)