10

Please consider the following two C++14 programs:

Program 1:

struct S { constexpr int f() const { return 42; } }; S s; int main() { constexpr int x = s.f(); return x; } 

Program 2:

struct S { constexpr int f() const { return 42; } }; int g(S s) { constexpr int x = s.f(); return x; } int main() { S s; return g(s); } 

Are neither, either or both of these programs ill-formed?

Why/why not?

3 Answers 3

12

Both programs are well-formed. The C++14 standard requires that s.f() be a constant expression because it is being used to initialize a constexpr variable, and in fact it is a core constant expression because there's no reason for it not to be. The reasons that an expression might not be a core constant expression are listed in section 5.19 p2. In particular, it states that the evaluation of the expression would have to do one of several things, none of which are done in your examples.

This may be surprising since, in some contexts, passing a non-constant expression to a constexpr function can cause the result to be a non-constant expression even if the argument isn't used. For example:

constexpr int f(int) { return 42; } int main() { int x = 5; constexpr auto y = f(x); // ill-formed } 

However, the reason this is ill-formed is because of the lvalue-to-rvalue conversion of a non-constant expression, which is one of the things that the evaluation of the expression is not allowed to do. An lvalue-to-rvalue conversion doesn't occur in the case of calling s.f().

Sign up to request clarification or add additional context in comments.

Comments

2

I can't seem to find a compelling passage or example in the standard that directly addresses the issue of calling a constexpr member function on a non-constexpr instance, but here are some that may be of help (from draft N4140):

[C++14: 7.1.5/5]:

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { return f(true); } // ill-formed, no diagnostic required 

From this I take that the program is not outright ill-formed just because a constexpr function has a possible non-constexpr path.

[C++14: 5.19]:

int x; // not constant struct A { constexpr A(bool b) : m(b?42:x) { } int m; }; constexpr int v = A(true).m; // OK: constructor call initializes // m with the value 42 constexpr int w = A(false).m; // error: initializer for m is // x, which is non-constant 

This is somewhat closer to your example programs, here a constexpr constructor may reference a non-constexpr variable depending on the value of the argument, but there is no error if this path is not actually taken.

So I don't think either program you presented should be ill-formed, but I cannot offer convincing proof :)

Comments

-4

This sounds like a quiz question, and not presented by a student, but the professor testing the public on stackoverflow, but let's see...

Let's start with the One Definition Rule. It's clear neither version violates that, so they both pass that part.

Then, to syntax. Neither have syntax failures, they'll both compile without issue if you don't mind the potential blend of a syntax and semantic issue.

First, the simpler semantic issue. This isn't a syntax problem, but f(), in both versions, is the member of a struct, and the function clearly makes no change to the owning struct, it's returning a constant. Although the function is declared constexpr, it is not declared as const, which means if there were some reason to call this as a runtime function, it would generate an error if that attempt were made on a const S. That affects both versions.

Now, the potentially ambiguous return g(S()); Clearly the outer g is a function call, but S may not be so clear as it would be if written return g(S{}); With {} initializing S, there would be no ambiguity in the future should struct S be expanded with an operator() (the struct nearly resembles a functor already). The constructor invoked is automatically generated now, and there is no operator() to create confusion for the compiler at this version, but modern C++14 is supposed to offer clearer alternatives to avoid the "Most Vexing Parse", which g(S()) resembles.

So, I'd have to say that based on semantic rules, they both fail (not so badly though).

14 Comments

Omitting the const on the member functions was unintentional, though I'm not sure if it impacts the ill-formedness (or otherwise) of the examples. I've added const just in case.
I've also changed the default constructed S to be a local variable in the second example, for clarity.
I'm not 100% myself, however, it only invokes the semantic issue that the function obviously operates as const, but isn't (or wasn't) declared const, and so would generate an error if an S were passed as a const & S or similar from which f() was called. I actually thought you were testing US out here ;)
Yes, and that doesn't happen in the examples, so why I don't think it effects the answer.
I'm also unclear as to your answer. You say it fails based on semantic rules? To which semantic rules are you refering?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.