While testing out aggregate types I tried using boost::proto::is_aggregate in order to check if the types that I'm creating are truly aggregate. I wrote this code:
#include <iostream>
#include <boost\proto\traits.hpp>
struct IsAggregate
{
IsAggregate &operator=(IsAggregate const &rhs) {}
};
int main()
{
std::cout << std::boolalpha;
std::cout << boost::proto::is_aggregate<IsAggregate>() << std::endl;
return 0;
}
And I expected the output to be true since aggregate types can define a copy-assignment operator (according to this: What are Aggregates and PODs and how/why are they special?)
But the output is false.
I also used the aggregate classes inside of the previous answer which should have returned true but instead returned false.
This was tested on Boost 1.5.9 with both the Intel Compiler and MSVC.
Any ideas on why this has happened?
Proto's trait is clearly not intended for wider use.
It defaults to just call PODs aggregates and then simply lists 3 - library internal - types as aggregates explicitly. The behaviour described in the documentation indicates it was there to scratch an itch (the make<>
functions needed a way to know which types go with T{}
and which go with T()
).
Taking a step back, you should probably reconsider your reasons for wanting this trait in the first place. You can most likely make your concept checks more specific.
Proposals to add a trait to the standard library have been met with broadly supported rejection:
I have reservations about this trait. We made a mistake adding useless traits like is_pod and is_literal_type, and we shouldn't compound that mistake by adding more useless traits.
In my view, type traits should capture observable properties of types (such as, "can I direct-initialize this type from that braced-init-list?"), and not core language ephemera (such as "does this type obey the literal type rules, which are a crutch for requiring a compiler diagnostic?" or "will braced initialization on this type perform aggregate initialization or will it call a constructor?").
Also, I think that a type should be able to switch between being an aggregate and providing an equivalent constructor set without worrying that someone might be observing the difference.
I have looked at the requirements and have concluded it's hard/impossible to make a fool-proof implementation. The first requirement I was unable to address programmatically is
no base classes (clause 10)
There's no way to tell a class has no (public, empty etc.) base class.
So the best thing I can come up is an approximation only:
template <typename T>
struct is_aggregate : std::integral_constant<bool,
std::is_pod<T>() or
(
std::is_trivially_constructible<T>()
and std::is_trivially_destructible<T>()
and std::is_standard_layout<T>()
and not std::is_polymorphic<T>()
)
>
{ };
Given the limitations, it seems to do its job fairly well, at least /a lot/ better than Proto's internal trait.
CAVEAT This doesn't address the subtle changes in c++11/c++14 (related to in-class member initializers e.g.). Also some of the traits used to compose the above approximation have known issues on various compiler versions (notably MSVC, as I remember), so do not trust this trait to be accurate across all language/library versions.
#include <iostream>
#include <type_traits>
#include <string>
template <typename T>
struct is_aggregate : std::integral_constant<bool,
std::is_pod<T>() or
(
std::is_trivially_constructible<T>()
and std::is_trivially_destructible<T>()
and std::is_standard_layout<T>()
and not std::is_polymorphic<T>()
)
>
{ };
namespace simple { // ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
};
static_assert(is_aggregate<X>(), "");
void foo() { X x { 42 }; (void) x; }
}
namespace usr_defined_ctor { // NOT ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
X() {}
};
static_assert(!is_aggregate<X>(), "");
//void foo() { X x { 42 }; (void) x; }
}
namespace defaulted_ctor { // ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
X() = default;
};
static_assert( is_aggregate<X>(), "");
void foo() { X x { 42 }; (void) x; }
}
namespace static_data_members { // ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
X() = default;
static const bool yeah = true;
private:
static const bool no = true;
protected:
static const std::string problem;
};
bool const X::yeah;
bool const X::no;
std::string const X::problem = "whatsoever";
static_assert( is_aggregate<X>(), "");
void foo() { X x { 42 }; (void) x; }
}
namespace private_non_static_data_members { // NOT ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { (void) oops; return *this; }
private:
bool oops;
};
static_assert(!is_aggregate<X>(), "");
//void foo() { X x { 42, true }; (void) x; }
}
namespace protected_non_static_data_members { // NOT ok
struct X {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
protected:
bool oops;
};
static_assert(!is_aggregate<X>(), "");
//void foo() { X x { 42, true }; (void) x; };
}
namespace have_base_class { // NOT ok
struct B {};
struct X : B {
int x;
X &operator=(X const &/*rhs*/) { return *this; }
};
static_assert(is_aggregate<X>(), ""); // FALSE POSITIVE: the below fails to compile
//void foo() { X x { 42 }; (void) x; };
}
int main() { }
Coliru compiles this cleanly (without static asserts) on:
g++ -std=c++0x -O2 -Wall -pedantic -pthread main.cpp
g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp
g++ -std=c++1y -O2 -Wall -pedantic -pthread main.cpp
g++ -std=c++1z -O2 -Wall -pedantic -pthread main.cpp
clang++ -std=c++0x -O2 -Wall -pedantic -pthread main.cpp
clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp
clang++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp
clang++ -std=c++1y -O2 -Wall -pedantic -pthread main.cpp
clang++ -std=c++1z -O2 -Wall -pedantic -pthread main.cpp
clang++ -stdlib=libc++ -std=c++0x -O2 -Wall -pedantic -pthread main.cpp
clang++ -stdlib=libc++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp
clang++ -stdlib=libc++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp
clang++ -stdlib=libc++ -std=c++1y -O2 -Wall -pedantic -pthread main.cpp
clang++ -stdlib=libc++ -std=c++1z -O2 -Wall -pedantic -pthread main.cpp