"if constexpr" interaction with "try in constexpr function" warning
Asked Answered
P

1

7

I claim that this program ought to be well-formed: it declares a constexpr member function of S<int>. However, both GCC and Clang reject this program.

template<class T>
struct S {
    constexpr int foo() {
        if constexpr (std::is_same_v<T, int>) {
            return 0;
        } else {
            try {} catch (...) {}
            return 1;
        }
    }
};

int main()
{
    S<int> s;
    return s.foo();  // expect "return 0"
}

GCC says:

error: 'try' in 'constexpr' function

Clang says:

error: statement not allowed in constexpr function

Neither of them seem to notice that the "try" statement is located in a discarded branch of the if constexpr statement.

If I factor the try/catch out into a non-constexpr member function void trycatch(), then both Clang and GCC are happy with the code again, even though its behavior ought to be equivalent to the unhappy version.

template<class T>
struct S {
    void trycatch() {
        try {} catch (...) {}
    }
    constexpr int foo() {
        if constexpr (std::is_same_v<T, int>) {
            return 0;
        } else {
            trycatch();  // This is fine.
            return 1;
        }
    }
};

Is this

  • a bug in both GCC and Clang?
  • a defect in the Standard, which GCC and Clang are faithfully implementing?
  • a Quality of Implementation issue due to the "conditional constexprness" of foo()?

(Irrelevant background: I'm implementing constexpr any::emplace<T>() for an allocator-aware version of any whose allocator might be constexpr-per-P0639 (i.e. it might lack a deallocate member function) or might not. In the former case we don't want or need the try; in the latter case we need the try in order to call deallocate if the constructor of T throws.)

Peccant answered 17/10, 2017 at 0:7 Comment(1)
They could be right in rejecting thr program even if the equivalent one with indirection is also correctly accepted if the case is an ill formed program with no diagnostic required.Recension
S
11

The compilers are obeying the Standard. C++17 draft N4659 says ([dcl.constexpr]/(3.4.4)):

The definition of a constexpr function shall satisfy the following requirements:

  • ...

  • its function-body shall be = delete, = default, or a compound-statement that does not contain

    • ...

    • a try-block, or

    • ...

And none of the rules for "discarded statements" such as the else statement in your S<int>::foo override this rule. The only special things specified about discarded statements are that discarded statements are not instantiated, odr-uses within discarded statements do not cause a definition for the used declaration to be required, and discarded return statements are ignored when determining the true return type of a function with placeholder return type.

I did not see any existing C++ Issue discussing this, and the paper P0292R1 which proposed if constexpr does not address interactions with constexpr functions.

Sizemore answered 17/10, 2017 at 0:40 Comment(6)
A template has no function body, only an instantiated function template has. The draft standard says, that discareded statements of a consexpr if will not be instantiated. Therefore any discarded try block does not exist in an instantiated function template and cannot be reason to be rejected by a compiler.Adelleadelpho
@Adelleadelpho The term "function-body" refers to the syntax grammar, not semantics, so a function template definition definitely has one. And you'll need to justify your claim that discarded statements "do not exist in" (or to match the constexpr function wording, are not "contained in") a function template specialization. And if that is the case, why does the Standard need to bother to state exceptions about odr-uses and return statements in a discarded statement?Sizemore
I don't understand this answer yet. You say "The only special things specified about discarded statements are that discarded statements are not instantiated,". In this example "{ try {} catch (...) {} return 1; }" would be a discarded statement. And if it's not instantiated, it's not there. So the instantiated function would not contain a try-block. So what am I missing?Colombo
So juding from your comment, do you state that not instantiated function template specializations and their containing statements are already existing? Do you say that in "template<typename T> void f() { T t; }" we already try to create a "void t;" or "AnyThinkableType t;" and therefore that snippet is ill-formed? I don't understand the reasoning in that comment yet.Colombo
@JohannesSchaub-litb I'm trying to avoid "exist" because the Standard doesn't use it in that way. And no, I'd agree that { T t; } there is fine, because a dependent construct is only ill-formed if instantiated with template arguments that result in a rule violation or if there is no possible set of template arguments that would make it valid. But I'd say a class template specialization can contain member function and friend function definitions that are never instantiated, and a function template specialization can contain discarded statements, ...Sizemore
... because the Standard doesn't say anything about removing or replacing such things. If you say "if it's not instantiated, it's not there", you might imply coliru.stacked-crooked.com/a/f9b23bc8d27260c1 is well-formed. But clang and gcc disagree. A<int> does not have an implicitly declared copy constructor A(const A&) because A(A&, int) is a user-declared copy constructor, because its second parameter has a default argument - which is never instantiated. I'm not saying this all makes sense, and maybe a Defect is in order, but I still think this is what the Standard says now.Sizemore

© 2022 - 2024 — McMap. All rights reserved.