Considering the following code
struct S
{
constexpr S() = default;
constexpr S(S const &) = default;
constexpr S(S &) = default;
constexpr S(S &&) = default;
#if 1
S & operator = (S const &) = default;
S & operator = (S &) = default;
S & operator = (S &&) = default;
#else
constexpr S & operator = (S const &) = default;
constexpr S & operator = (S &) = default;
constexpr S & operator = (S &&) = default;
#endif
~S() = default;
};
struct U
{
union
{
S s;
};
constexpr U() : s{} { ; }
};
inline constexpr bool test()
{
U v;
U w;
v = w;
return true;
}
static_assert(test());
I found out that there is contradiction.
I want to use union (or euqally union-like class) in constexpr.
Version #if 1 gives an error (error: constexpr function never produces a constant expression):
main.cpp:31:23: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
inline constexpr bool test()
^
main.cpp:35:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
v = w;
^
main.cpp:19:8: note: declared here
struct U
^
main.cpp:39:15: error: static_assert expression is not an integral constant expression
static_assert(test());
^~~~~~
main.cpp:35:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
v = w;
^
main.cpp:39:15: note: in call to 'test()'
static_assert(test());
^
main.cpp:19:8: note: declared here
struct U
^
2 errors generated.
But version #if 0 also give an error (error: defaulted definition of copy assignment operator is not constexpr):
main.cpp:12:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr S & operator = (S const &) = default;
^
main.cpp:13:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr S & operator = (S &) = default;
^
main.cpp:14:5: error: defaulted definition of move assignment operator is not constexpr
constexpr S & operator = (S &&) = default;
^
main.cpp:35:7: error: object of type 'U' cannot be assigned because its copy assignment operator is implicitly deleted
v = w;
^
main.cpp:24:11: note: copy assignment operator of 'U' is implicitly deleted because field 's' has no copy assignment operator
S s;
^
4 errors generated.
LIVE EXAMPLE
All above is for version of clang 3.7.0, but newer version clang 3.8.0 (trunk 253951) nothing says about constexpr S & operator = (S const &) = default; (it allows to compile such a code), but consequences are the same.
For trivial copy/move-constructors, but user-provided assignment operators analogous code (construction) compiles fine.
I think the problem (bug) is in user-declared special functions, but it should be legal at all.
Also please look at the last and last but one clauses of this code. Quite well-designed (at my mind) variant becomes useless in constexpr, due to difference in interpretation by compiler of next definitions:
struct S {};
and:
struct S
{
constexpr S() = default;
constexpr S(S const &) = default;
constexpr S(S &) = default;
constexpr S(S &&) = default;
S & operator = (S const &) = default;
S & operator = (S &) = default;
S & operator = (S &&) = default;
~S() = default;
};
But both is equivalent at my mind (the second recognized by compiler as not POD, but who cares?).
Why defaulted assignment operator of empty struct is not constexpr?
UPDATE:
Removing the declaration of destructor makes problem to disappear. So even user-declared (not user-provided) destructor make implicitly defined defaulted assignment operators to be not marked as constexpr and to be non-trivial. It definitely bug because no one source telling about this.
Related
I bumped in GCC error, which can be demonstrated with the following simplified program. Base struct A has an operator == with some argument type other than A. Child struct B defines default spaceship operator <=>, which shall implicitly define operator == as well. But since no appropriate equality operator is found in the base class, B::operator == must be implicitly deleted. Instead, the resulting struct B appears broken in a way that no object of it can be created:
#include <compare>
struct A {
std::strong_ordering operator <=>(const A &) const = default;
bool operator==(int) const;
};
struct B : A {
virtual std::strong_ordering operator <=>(const B &) const noexcept = default;
};
B b; //GCC error here
Demo: https://gcc.godbolt.org/z/584nhfxxs
Error message:
error: use of deleted function 'virtual constexpr bool B::operator==(const B&) const'
9 | virtual std::strong_ordering operator <=>(const B &) const noexcept = default;
| ^~~~~~~~
<source>:9:34: note: 'virtual constexpr bool B::operator==(const B&) const' is implicitly deleted because the default definition would be ill-formed:
<source>:8:8: error: no match for 'operator==' (operand types are 'A' and 'A')
8 | struct B : A {
| ^
<source>:5:10: note: candidate: 'bool A::operator==(int) const' (reversed)
5 | bool operator==(int) const;
| ^~~~~~~~
<source>:5:21: note: no known conversion for argument 1 from 'A' to 'int'
5 | bool operator==(int) const;
Is it simply a bug in GCC?
I have a simple primitive type wrapper:
template <typename T>
class Scalar {
public:
explicit Scalar(T value) : value{value} {}
Scalar(Scalar&& other) = default;
Scalar& operator=(Scalar&& other) = default;
Scalar(const Scalar& other) = default;
Scalar& operator=(const Scalar& other) = default;
template <typename U>
explicit operator Scalar<U>() {
return Scalar<U>{static_cast<U>(this->value)};
}
inline T getValue() const noexcept { return this->value; }
private:
T value;
};
Casting Scalar values works well, but somehow it fails for references, e.g.
auto a = Scalar<double>{2.54};
Scalar<int> b = static_cast<Scalar<int>>(a); // works
const auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c); // fails
Here's the compilation error (can be checked here http://rextester.com/GOPYU13091), any ideas what might be the issue here?
source_file.cpp: In function ‘int main()’:
source_file.cpp:31:47: error: no matching function for call to ‘Scalar(const Scalar<double>&)’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
source_file.cpp:12:3: note: candidate: Scalar<T>::Scalar(const Scalar<T>&) [with T = int] <near match>
Scalar(const Scalar& other) = default;
^
source_file.cpp:12:3: note: conversion of argument 1 would be ill-formed:
source_file.cpp:31:47: error: could not convert ‘c’ from ‘const Scalar<double>’ to ‘const Scalar<int>&’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
source_file.cpp:10:3: note: candidate: Scalar<T>::Scalar(Scalar<T>&&) [with T = int] <near match>
Scalar(Scalar&& other) = default;
^
source_file.cpp:10:3: note: conversion of argument 1 would be ill-formed:
source_file.cpp:31:47: error: could not convert ‘c’ from ‘const Scalar<double>’ to ‘Scalar<int>&&’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
This is a const vs non-const issue, not a reference vs object issue.
Using
auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c);
works for me.
By changing the user defined conversion operator to const member function
template <typename U>
explicit operator Scalar<U>() const {
return Scalar<U>{static_cast<U>(this->value)};
}
also makes sure that the following works.
const auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c);
I want to define constexpr values of a derived type (SBar), using a constructor whose only argument is a variable of the base class (SFoo), which is simply used initialize the base.
This works fine when the base class has no array member. However, when I add an array the derived values can no longer be constexpr. A simple copy of the base class does yield constexpr results, though.
I have explicitly defaulted all copy and move constructors just to be safe.
test.cpp
#define USE_ARRAY
struct SFoo
{
constexpr SFoo() =default;
constexpr SFoo(SFoo const&) =default;
constexpr SFoo(SFoo &) =default;
constexpr SFoo(SFoo &&) =default;
constexpr SFoo& operator = (SFoo const&) =default;
constexpr SFoo& operator = (SFoo &) =default;
constexpr SFoo& operator = (SFoo &&) =default;
# ifdef USE_ARRAY
constexpr SFoo(int const (&array)[1]) :
M_array{array[0]}
{}
int M_array[1] = {0};
# else
constexpr SFoo(int value) :
M_value{value}
{}
int M_value = 0;
# endif
};
struct SBar : SFoo
{
constexpr SBar() =default;
constexpr SBar(SBar const&) =default;
constexpr SBar(SBar &) =default;
constexpr SBar(SBar &&) =default;
constexpr SBar& operator = (SBar const&) =default;
constexpr SBar& operator = (SBar &) =default;
constexpr SBar& operator = (SBar &&) =default;
constexpr SBar(SFoo foo) : SFoo(foo) {}
};
// Instances:
# ifdef USE_ARRAY
constexpr int arg[1] = {3};
# else
constexpr int arg = 3;
# endif
constexpr SFoo foo(arg); // base "value" constructor is constexpr.
constexpr SFoo foo2(foo); // base copy constructor is constexpr.
constexpr SBar bar(foo); // (line 54): this line fails.
compiling with
clang++ -std=c++1z -c -o test.o test.cpp
yields
test.cpp:54:16: error: constexpr variable 'bar' must be initialized by a constant expression
constexpr SBar bar(foo);
^~~~~~~~
1 error generated.
however, everything works if I don't define USE_ARRAY.
Does anyone know why this is happening?
(I know std::array can help, but I'd rather use native arrays and understand the underlying problem).
So for clang it looks like there are a couple of fixes for this. You can change:
constexpr SBar(SFoo foo) : SFoo(foo) {}
to take foo by const reference:
constexpr SBar(const SFoo &info) : SFoo(info) {}
The other fix that seems to work is comment out the following copy constructor in sFoo:
//constexpr SFoo(SFoo &) =default;
I don't see immediately the language in the draft C++1z standard that make this change make sense.
On the other hand gcc complains about the copy constructor saying the implicit definitions would not be constexpr (see it live), for example:
error: explicitly defaulted function 'constexpr SFoo& SFoo::operator=(const SFoo&)' cannot be declared as constexpr because the implicit declaration is not constexpr
constexpr SFoo& operator = (SFoo const&) =default;
^
which is not obvious to me from my reading of 7.1.5 [dcl.constexpr] and 5.20 [expr.const].
As far as I can tell from my reading of section 12.8p26 the implicitly defined copy/move assignment should be constexpr. So gcc seems incorrect here.
It's not due to the array member. It's due to the assignment operators. If you make the following changes the code will work for both CLANG and GCC:
struct SFoo {
constexpr SFoo() = default;
constexpr SFoo(SFoo const&) = default;
constexpr SFoo(SFoo&&) = default;
constexpr SFoo(int const (&array)[1]) : M_array{array[0]} {}
int M_array[1] = {0};
};
struct SBar : SFoo {
constexpr SBar() = default;
constexpr SBar(SBar const&) = default;
constexpr SBar(SBar&&) = default;
constexpr SBar(SFoo info) : SFoo(info) {}
};
Live Demo
Your previous setting will work if instead of classical array you use a std::array:
struct SFoo {
constexpr SFoo() = default;
constexpr SFoo(SFoo const&) = default;
constexpr SFoo(SFoo &) = default;
constexpr SFoo(SFoo &&) = default;
constexpr SFoo& operator = (SFoo const&) = default;
constexpr SFoo& operator = (SFoo &) = default;
constexpr SFoo& operator = (SFoo &&) = default;
constexpr SFoo(std::array<int, 1> const &array) : M_array{array} {}
std::array<int, 1> M_array = {};
};
Live Demo
No as for the reason why. I'm still searching...
According to C++ reference std::swap is equivalent to
T c(std::move(a)); a=std::move(b); b=std::move(c);
This should allow to swap two non copiable but movable object. Therefore I don't understand why
#include<utility>
struct Foo {
Foo() = delete;
Foo(int) {};
Foo(Foo &) = delete;
Foo(Foo &&) {};
~Foo() {};
};
int main() {
Foo a(1),b(2);
std::swap(a,b);
}
is refused by the compiler with
In file included from /usr/include/c++/4.8/bits/stl_pair.h:59:0,
from /usr/include/c++/4.8/utility:70,
from swap.cpp:1:
/usr/include/c++/4.8/bits/move.h: In instantiation of ‘void std::swap(_Tp&, _Tp&) [with _Tp = Foo]’:
swap.cpp:13:16: required from here
/usr/include/c++/4.8/bits/move.h:176:11: error: use of deleted function ‘Foo& Foo::operator=(const Foo&)’
__a = _GLIBCXX_MOVE(__b);
^
swap.cpp:3:8: note: ‘Foo& Foo::operator=(const Foo&)’ is implicitly declared as deleted because ‘Foo’ declares a move constructor or move assignment operator
struct Foo {
^
In file included from /usr/include/c++/4.8/bits/stl_pair.h:59:0,
from /usr/include/c++/4.8/utility:70,
from swap.cpp:1:
/usr/include/c++/4.8/bits/move.h:177:11: error: use of deleted function ‘Foo& Foo::operator=(const Foo&)’
__b = _GLIBCXX_MOVE(__tmp);
note: this is with GCC 4.8 and 4.9 but clang complain as well.
You declared a move constructor. However, you need a move assignment operator for std::swap. You should add the following two operators:
auto operator=(const Foo& rhs) & -> Foo& = delete;
auto operator=(Foo&& rhs) & noexcept -> Foo&
{
// ...
return *this;
}
I had a problem in a highly templated code, and I isolated it in this example program (I compile with g++ 4.7.1) :
#include <iostream>
#include <initializer_list>
#include <type_traits>
#define OPTION 2
// Test class
template<typename T = double>
class MyClass
{
public:
// Constructors
MyClass(const MyClass<T>& source)
{std::cout<<"copy constructor"<<std::endl;}
#if OPTION == 1
template<typename T2 = T>
MyClass(const std::initializer_list<T2>& source)
{std::cout<<"init constructor"<<std::endl;} // OPTION 1
#elif OPTION == 2
template<typename T2 = T, class... Misc>
MyClass(const std::initializer_list<T2>& source, const Misc&... misc)
{std::cout<<"init+misc constructor"<<std::endl;} // OPTION 2
#endif
template<class... Misc>
explicit MyClass(const Misc&... misc)
{std::cout<<"misc constructor"<<std::endl;}
// Assignment
inline MyClass<T>& operator=(const MyClass<T>& rhs)
{std::cout<<"copy assignment"<<std::endl; return *this;}
template<class Misc>
inline MyClass<T>& operator=(const Misc& rhs)
{std::cout<<"misc assignment"<<std::endl; return *this;}
};
// Main
int main(int argc, char* argv[])
{
MyClass<double> x;
x = {4., 8., 15., 16., 23., 42.}; // CALL LINE
return 0;
}
The problem is the following. With OPTION == 1, all is ok, and the CALL LINE calls the "init constructor".
But with OPTION == 2, I have the following error message :
error: converting to ‘const MyClass<double>’ from initializer list would use
explicit constructor ‘MyClass<T>::MyClass(const Misc& ...) [with Misc = {double,
double, double, double, double, double}; T = double]’
The question is : why ? Is it normal or is it a bug of g++ ?
Furthemore, I need a workaround : I have to implement the option 2 constructor without option 1. The most obvious thing for me would be to block the call to the explicit constructor with an enable if, like :
template<class... Misc, class std::enable_if<SOMETHING>::type>
explicit MyClass(const Misc&... misc)
{std::cout<<"misc constructor"<<std::endl;}
My problem is : what can I write instead of SOMETHING in order to block every std::initializer_list<T2> (for all T2 types) ?
To the first question, about the error message:
The constructor in OPTION==2 is not considered as initializer-list constructor, because such a constructor is required to either have a single std::initializer_list<E> parameter (or such a reference) or all other parameters shall have default arguments. (8.5.4 List-initialization [dcl.init.list] #2).
Therefore, not finding an initializer-list constructor the compiler tries other constructors with argument list consisting of the elements of the initializer list, finds the explicit constructor and, since we're in the context of implicit conversion (initializing the parameter of the assignment operator), fails with the error message.