Giovanni Funchal
Giovanni Funchal

Reputation: 9200

Work around incomplete type in static assert

Is there a way to static_assert inside a class when the expression depends on the class type itself? Maybe delay the evaluation until the type is complete or after template instantiation?

Example code:

#include <type_traits>

template<typename T>
struct Test {
   T x = 0; // make non-trivial
   static_assert(std::is_trivial<Test<T>>::value, "");
};

int main() {
    // would like static assert failure, instead get 'incomplete type' error
    Test<int> test1;
    Test<float> test2;
    return 0;
}

Upvotes: 6

Views: 1823

Answers (5)

dyp
dyp

Reputation: 39141

I don't know if that's fully compliant but both gcc trunk and clang trunk accept this:

#include <type_traits>

template<typename T>
constexpr bool check_trivial()
{
    static_assert(std::is_trivial_v<T>);
    return std::is_trivial_v<T>;
}

template<typename T>
struct Test
{
   Test() noexcept(check_trivial<Test<T>>()) = default;

   T x;
};

struct nontrivial
{
    nontrivial(){}
};

int main() {
    // would like static assert failure, instead get 'incomplete type' error
    Test<int> test1;
    Test<nontrivial> test2;
    return 0;
}

It doesn't work when using the dtor instead of the ctor. YMMV.

This makes your type a non-aggregate though.


If your type has any mandatory member functions, you can put the static_assert in one of those function bodies. Member function bodies are evaluated after the end of the class definition, and therefore don't see an incomplete type.

For templates, the issue is that not all member functions are always instantiated. A static_assert in a non-instantiated member function won't fire. You need a call (any ODR-use actually) somewhere in the code to force instantiation.

Upvotes: 0

Cevik
Cevik

Reputation: 333

I was also looking for a solution with static_assert, but constraints work too:

#include <type_traits>

namespace Private {
template<typename T>
struct Test {
   T x = 0;
};
}

template<typename T>
requires std::is_trivial<Private::Test<T>>::value
using Test = Private::Test<T>;

int main() {
    Test<int> test1;
    Test<float> test2;
    return 0;
}

Upvotes: 2

Giovanni Funchal
Giovanni Funchal

Reputation: 9200

Here's a solution using a helper class and a type alias for indirection. I believe this has no drawbacks.

template<typename T>
struct TestImpl {
    T x = 0; // make non-trivial
};

template<typename T>
struct TestHelper {
    using type = TestImpl<T>;
    static_assert(std::is_trivial<type>::value, "");
};

template<typename T>
using Test = typename TestHelper<T>::type;

edit: Alternatively TestHelper can be moved into TestImpl:

template<typename T>
struct TestImpl {
    T x = 0; // make non-trivial

    struct Helper {
        using type = TestImpl;
        static_assert(std::is_trivial<type>::value, "");
    };
};

template<typename T>
using Test = typename TestImpl<T>::Helper::type;

Upvotes: 2

Matteo Italia
Matteo Italia

Reputation: 126867

(moving from the comments)

You can add an intermediate class to keep the logic, and let your clients instantiate a derived one, which just contains the static_asserts

#include <type_traits>

template<typename T>
struct TestImpl {
    T x = 0; // make non-trivial
};

template<typename T>
struct Test : TestImpl<T> {
    static_assert(std::is_trivial<TestImpl<T>>::value, "");
    using TestImpl<T>::TestImpl; // inherit constructors
};

Upvotes: 0

Gert Wollny
Gert Wollny

Reputation: 529

You can define a destructor and put the static_assert there, at least with g++ 5.4 -std=c++11 this works.

Upvotes: 0

Related Questions