2
#include <variant>

struct S {
    constexpr auto f() -> void {
        // deleting the next line creates an error
        if(std::holds_alternative<int>(m_var))
            m_var.emplace<double>(5.0);
    }
    std::variant<int, double> m_var;
};

int main() {
    return 0;
}

std::variant has a non-constexpr member function emplace(). In general you can't use that in constexpr functions. You can however if you surround that call by a condition that uses std::holds_alternative() on that type. Also other constexpr functions as long as they're member functions in that class.

I'm having trouble to understand what' going on. My first reaction was to say that's a bug. That condition can't possibly be more constexpr than no condition at all. But maybe that was premature. Can anyone shed some light on this? Why is it that emplace() is not constexpr but (equal-type) assignments are?

Edit: Maybe to expand a bit: One guess is that constructors and destructors of the involved variants could be non-constexpr and that's why emplace etc are not. But the fun thing is that you can use conditions like this to compile the function as constexpr even when you explicitly abuse a non-constexpr constructor. That voids that argument.

godbolt: here.

Basti
  • 2,228
  • 1
  • 19
  • 25
  • Does this answer your question? [calling non constexpr function from constexpr allowed in some conditions](https://stackoverflow.com/q/47726503) – L. F. Oct 15 '20 at 13:01
  • Duplicate of https://stackoverflow.com/questions/33716507/how-can-this-code-be-constexpr-stdchrono ? – Marshall Clow Oct 15 '20 at 13:01

1 Answers1

2

You don't actually need to delve much into std::variant to reason about this. This is mostly about how constant expressions work. constexpr functions must be defined in a way that allows for evaluation in a constant expression. It doesn't matter if for some arguments we run into something that can't appear in a constant expression, so long as for other arguments we obtain a valid constant expression. This is mentioned explicitly in the standard, with an exeample

[dcl.constexpr]

5 For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object ([basic.start.static]), the program is ill-formed, no diagnostic required. [ Example:

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

struct B {
  constexpr B(int x) : i(0) { }         // x is unused
  int i;
};

int global;

struct D : B {
  constexpr D() : B(global) { }         // ill-formed, no diagnostic required
                                        // lvalue-to-rvalue conversion on non-constant global
};

 — end example ]

See how f(bool) is a valid constexpr function? Even though a throw expression may not be evaluated in a constant expression, it can still appear in a constexpr function. It's no problem so long as constant evaluation doesn't reach it.

If there is no set of arguments for which a constexpr function can be used in a constant expression, the program is ill-formed. No diagnostic is required for this sort of ill-formed program because checking this condition from the function definition alone is intractable in general. Nevertheless, it's invalid C++, even if the compiler raises no error. But for some cases, it can be checked, and so a compiler could be obliged raise a diagnostic.

Your f without a condition falls into this category of ill-formed constructs. No matter how f is called, its execution will result in invoking emplace, which cannot appear in a constant expression. But it's easy enough to detect, so your compiler tells you it's a problem.

Your second version, with the condition, no longer invokes emplace unconditionally. Now its conditional. The condition itself is relying on a constexpr function, so it's not immediately ill-formed. Everything would depend on the arguments to the function (this included). So it doesn't raise an error immediately.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • So a successful constexpr compilation involves no guarantees at all, ever? Since it's ill-formed, but no error is required by the standard and therefore up to luck? Did I get that right? – Basti Oct 15 '20 at 13:12
  • @Basti - No, there are definitely guarantees. If a constant expression is evaluated and tries to "execute" a construct that is forbidden, you will get a diagnostic. This specification says that in the case where absolutely no evaluation will ever result in a constant expression, the program is already invalid. The NDR part is there due to the intractability of checking such an error from the definition alone. It basically excuses compiler writers from trying to solve the halting problem. – StoryTeller - Unslander Monica Oct 15 '20 at 13:16
  • When evaluated sure, but I meant just a bare function definition. Even if *no* ealuation could ever be const, there's no _guaranteed_ error then, right? I had that problem recently https://stackoverflow.com/questions/64220243 where that function could - with any T at all - never be possibly constexpr and still compiled fine. – Basti Oct 15 '20 at 13:18
  • @Basti - In that case, no, there are no standard imposed requirements to get a diagnostic. It's not so much up to chance however. It's just a matter of QoI, which may vary. – StoryTeller - Unslander Monica Oct 15 '20 at 13:21
  • Sure, got it now. The concept of errors not being required to report seems strange though. – Basti Oct 15 '20 at 13:24