`static constexpr` function called in a constant expression is...an error? [duplicate]
Asked Answered
I

1

41

I have the following code:

class MyClass
{
  static constexpr bool foo() { return true; }
  void bar() noexcept(foo()) { }    
};

I would expect that since foo() is a static constexpr function, and since it's defined before bar is declared, this would be perfectly acceptable.

However, g++ gives me the following error:

 error: ‘static constexpr bool MyClass::foo()’ called in a constant expression

This is...less than helpful, since the ability to call a function in a constant expression is the entire point of constexpr.

clang++ is a little more helpful. In addition to an error message stating that the argument to noexcept must be a constant expression, it says:

note: undefined function 'foo' cannot be used in a constant expression
note: declared here
static constexpr bool foo() { return true; }
                      ^

So...is this a two-pass-compilation problem? Is the issue that the compiler is attempting to declare all the member functions in the class before any of them are defined? (Note that outside of the context of a class, neither compiler throws an error.) This surprises me; intuitively, I don't see any reason for static constexpr member functions not to be useable in any and all constant expressions, inside the class or out.

Irradiance answered 10/4, 2015 at 0:13 Comment(13)
I'm guessing it has something to do with the requirement on a core constant expression that it must not contain "A function call to a constexpr function which is declared, but not defined [...]"Webbed
open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1255; open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1626Sartorial
@MattMcNabb But the body of bar isn't complete before the noexcept expression that uses it in your example, so there's no inconsistency.Irradiance
@Sartorial I am puzzled by your ability to find these gems. You should write an answer, as this is highly non-obvious.Louislouisa
The links from T.C. say the standard says a constexpr can not be referenced until the class declaration is done (}).Thaothapa
@Sartorial Awesome find, though I find it perplexing that the people writing those items are apparently promoting an explicit prohibition for the behavior I expected; since it seems potentially useful and I don't see any logical inconsistencies, I'm not sure why the next standard wouldn't explicitly allow such usage (e.g. by requiring that static constexpr member functions be fully defined as soon as they are encountered, or something).Irradiance
@KyleStrand maybe my example wasn't so good. but currently there is no dependence on which order functions are defined; or which order they are declared in a class. Your proposal would introduce that, which could be a can of worms. The auto example in CWG 1255 illustrates one of the problems.Webbed
@MattMcNabb I think there's already a dependence on declaration order: for instance, this code is invalid: void bar() noexcept(noexcept(foo())); static constexpr bool foo(); (Note that declaring foo before bar is legal.) Also, the auto example is not, in fact, a problem: there is no reason decltype(f()) shouldn't be equivalent to decltype(42) in that context.Irradiance
static const == constexpr, so you should use constexpr bool foo() { return true; } instead of static constexpr bool foo() { return true; }.Corlisscorly
I would have to agree with @MattMcNabb here, introducing a dependence on ordering is problematic as we can see from 3.3.7/1Polybasite
@Corlisscorly That's not true. For non-literal types (non-POD types or types without constexpr constructors), non-static member functions are illegal. For literal types, static and non-static member functions are different in the typical way (non-static member functions require a valid instance to call, and can access member data).Irradiance
@ShafikYaghmour Since matching declarations must refer to the same definition, I don't think my suggestion would make it possible to change program behavior/meaning based on reordering; it would just make certain orderings valid in some situations where currently no ordering is valid. So it wouldn't be a violation of 3.3.7 as I understand it.Irradiance
@Corlisscorly I just realized you may be thinking of member variable qualifiers, for which, yes, constexpr implies static.Irradiance
I
23

As T.C. demonstrated with some links in a comment, the standard is not quite clear on this; a similar problem arises with trailing return types using decltype(memberfunction()).

The central problem is that class members are generally not considered to be declared until after the class in which they're declared is complete. Thus, regardless of the fact that foo is static constexpr and its declaration precedes that of bar, it cannot be considered "available" for use in a constant expression until MyClass is complete.

As pointed out by Shafik Yaghmour, there is some attempt within the standard to avoid a dependency on the ordering of members within a class, and obviously allowing the example in the original question to compile would introduce an ordering dependency (since foo would need to be declared before bar). However, there is already a minor dependency on ordering, because although constexpr functions can't be called inside noexcept, a noexcept expression itself might depend on an earlier declaration inside the class:

class MyClass
{
    // void bar() noexcept(noexcept(foo())); // ERROR if declared here
    static constexpr bool foo();
    void bar() noexcept(noexcept(foo())); // NO ERROR
}

(Note that this is not actually a violation of 3.3.7, since there is still only one correct program that is possible here.)

This behavior may actually be a violation of the standard; T.C. points out (in a comment below) that foo here should actually be looked up in the scope of the whole class. Both g++ 4.9.2 and clang++ 3.5.1 fail with an error when bar is declared first but compile with no errors or warnings when foo is declared first. EDIT: clang++ trunk-revision 238946 (from shortly before the release of 3.7.0) does not fail when bar is declared first; g++ 5.1 still fails.

Intriguingly, the following variation causes a "different exception specifier" with clang++ but not with g++:

class MyClass
{
  static constexpr bool foo2();
  void bar2() noexcept(noexcept(foo2()));
};

constexpr bool MyClass::foo2() { return true; }
void MyClass::bar2() noexcept(noexcept(MyClass::foo2())) { }

According to the error, the noexcept specification for the declaration of bar2 evaluates to noexcept(false), which is then considered a mismatch for noexcept(noexcept(MyClasss::foo2())).

Irradiance answered 15/4, 2015 at 23:9 Comment(11)
That's a bit surprising. Names in exception-specifications are supposed to be looked up in the scope of the whole class ([basic.lookup.unqual]/p8, [basic.scope.class]/p1). I wonder if this is just the compilers at issue being nonconforming.Sartorial
@Sartorial ....Huh. So you're saying that the above code snippet should be valid regardless of the declaration order? Wouldn't that require some sort of two-pass declaration scheme?Irradiance
You already have that with member function bodies and NSDMIs; the rule isn't exactly new.Sartorial
@Sartorial I'm not sure I immediately grasp the implications there. Could you give an example?Irradiance
The standard says that you can do struct C { int *x = &y; int y; }; and of course struct C { int x() { return y; } int y; }; In both cases you are allowed to use a class member before its declaration, so obviously parsing of at least some part of the member declarations must be delayed until the class is complete.Sartorial
this version with noexcept compiles, but does not work. static constexpr uint32_t getCount(); ... constexpr uint32_t MyClass:getCount() {return 5;} actually gives 0. :(Spiniferous
@Spiniferous Maybe I'm misunderstanding what you mean, but I can't reproduce that in Compiler Explorer: godbolt.org/z/fabTTac6YIrradiance
@KyleStrand sorry, I meant with constexpr function is used inside class, same way as in this answer example. Like: godbolt.org/z/ne3j9E8G7Spiniferous
@Spiniferous That's the noexcept operator, which is correctly returning false because getCount is not specified to be noexcept (which would require the noexcept specifier).Irradiance
noexcepet omits the compile error but overrides the actual return value. Is there any workaround not requiring c++20 that tells the compiler foo has been properly defined?Gooden
@TJM: class Outer { class Helper { static constexpr bool foo() { return true; } }; void bar() noexcept(Helper::foo()) { } }; should be fine, since Outer::Helper is completed at the point it's needed.Thill

© 2022 - 2024 — McMap. All rights reserved.