7

Consider the following two programs:

#include<variant>
#include<iostream>

constexpr auto f() {
    using T = std::variant<bool, int>;
    T t(false);
    t = T(true);
    return std::get<bool>(t);
}

template<auto V> 
void print() { std::cout << V << "\n"; }

int main() {
    print<f()>();
}

and

#include<variant>
#include<iostream>

constexpr auto f() {
    using T = std::variant<bool, int>;
    T t(false);
    t = T(42);
    return std::get<int>(t);
}

template<auto V> 
void print() { std::cout << V << "\n"; }

int main() {
    print<f()>();
}

GCC compiles both of these and outputs the expected results. Clang does not compile any of them with the following error message in both cases:

<source>:4:16: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr auto f() {
               ^
<source>:7:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
    t = T(42);
      ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/variant:1095:16: note: declared here
      variant& operator=(variant&&) = default;

Are the two programs well-formed? If not, why?

Also if they are not well-formed, is the error message Clang gives appropriate? According to [variant.assign] the move assignment operator should be constexpr.

Furthermore according to (7.4) the assignment in the second example should behave equivalent to emplace<int>(...) which is not declared constexpr ([variant.mod]). Does this imply the second example is ill-formed because the template argument cannot be evaluated as constant expression or does the wording allow/require this behavior?

EDIT:

Based on the comments it seems that Clang compiles and ouputs the correct results if libc++ is used and the error occurs only with libstdc++. Is this an incompatibility between the standard library and compiler?

On https://godbolt.org/:

Works in both cases:

  • GCC 8.2.0 "-std=c++17"
  • Clang 7.0.0 "-std=c++17 -stdlib=libc++"

Does not work in either case:

  • Clang 7.0.0 "-std=c++17"
  • cannot reproduce; which versions of g++ and clang++ are you using? – max66 Dec 04 '18 at 17:17
  • What if you use `-stdlib=libc++` – Shafik Yaghmour Dec 04 '18 at 17:18
  • 1
    Looks like your library has a bug as `variant& operator=(variant&&) = default;` should be `constexpr variant& operator=(variant&&) = default;` – NathanOliver Dec 04 '18 at 17:18
  • clang accepts it with libc++ [Demo](https://godbolt.org/z/Q4MwhH). – Jarod42 Dec 04 '18 at 17:19
  • Show what compilation flags you're using (especially those that select C++ standards version to use). – Toby Speight Dec 04 '18 at 17:19
  • GCC 8.2.0 and Clang 7.0.0 on https://godbolt.org/ and https://wandbox.org/ for output test. I have not tested libc++, flags are `-Wall -Wextra -std=c++17`. –  Dec 04 '18 at 17:19
  • sorry but... compilig your second program with clang++ 7.0.0 ("clang++ prog.cc -Wall -Wextra -std=c++17 -pedantic-errors") in wandbox, all goes well. No error also with the first one. – max66 Dec 04 '18 at 17:23
  • @NathanOliver According to https://stackoverflow.com/questions/36161188/is-a-defaulted-constructor-assignment-noexcept-constexpr-by-default it might be implicitly `constexpr` depending on the context. Then I still don't see why GCC and Clang would interpret it differently. –  Dec 04 '18 at 17:23
  • @max66 Yes, sorry. I did not test it there with clang, because clang already failed to compile on godbolt. So it seems this is an incompatibility between clang and libstdc++? –  Dec 04 '18 at 17:24
  • Interesting. Clang seems to know that move assignment is trivial. I wonder why it thinks a trivial function is not constexpr. – cpplearner Dec 05 '18 at 10:10
  • 1
    Not an answer but similar [clang bug report](https://bugs.llvm.org/show_bug.cgi?id=38526) – Jans Dec 06 '18 at 21:31
  • Using VC++ 2017 with /std:c++17 Both programs compile and outputs the expected results. – Julien Villemure-Fréchette Dec 07 '18 at 15:27

1 Answers1

2

This looks like a clang bug we can see from the libstdc++ variant header that the move assignment operator is indeed not marked constexpr:

variant& operator=(variant&&) = default;

but a defaulted and implicitly defined move assignment operator can still be constexpr, we can see this from [class.copy.assign]p10 (emphasis mine):

A copy/move assignment operator for a class X that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) (e.g., when it is selected by overload resolution to assign to an object of its class type), when it is needed for constant evaluation ([expr.const]), or when it is explicitly defaulted after its first declaration. The implicitly-defined copy/move assignment operator is constexpr if

  • (10.1) X is a literal type, and
  • (10.2) the assignment operator selected to copy/move each direct base class subobject is a constexpr function, and
  • (10.3) for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is a constexpr function.

From what I can can tell the libstdc++ implementation should fit all these cases, it is a literal type, it does not have non static data members and the assignment operator for all its bases should be constexpr as well.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Thank you for the answer. Originally I wanted to primarily know whether both programs are well-formed, see the multiple questions above `EDIT:`. If possible can you answer these as well? –  Dec 12 '18 at 05:24
  • @user10605163 there are a lot of questions that discuss what is valid in a constexpr function [for example here](https://stackoverflow.com/a/34280783/1708801) it quotes the full quote and `f()` is well formed there. – Shafik Yaghmour Dec 12 '18 at 05:47