How to enforce the C++ named requirement "Container"

115 Views Asked by At

I am trying to make a template container class and I want it to conform to the "Container" named requirement as best I can. I am looking at this cppreference link and at the bottom it says:

Other requirements
C (Container)
    DefaultConstructible
    CopyConstructible
    EqualityComparable
    Swappable
T (Type)
    CopyInsertable
    EqualityComparable
    Destructible

I want to add some static asserts in my code such that I never accidentally regress any functionality, and I am looking into adding it inside the class definition. Here is a minimal representation of my code:

#include <iostream>
#include <type_traits>

template <typename T>
class MyContainer {
    public:
        MyContainer() = default;

        static_assert(std::is_default_constructible<MyContainer>::value, "MyContainer is not default constructible!");
};

int main() {
    // instantiate the object so that static assert may be evaluated
    MyContainer<int> obj1;

    std::cout << "All checks passed." << std::endl;

    return 0;
}

However, when trying to compile this (at the moment using g++ 9.4), the compilation fails on the static assert.

Why is this failing?

Are static asserts even meant to be used this way? For example looking at my c++ standard library implementation of std::vector class, I can clearly see they use some static asserts like this (although not for checking that the "Container" requirements are satisfied) Also, any provided answer must be portable for all major compilers (g++, clang++ and msvc)

2

There are 2 best solutions below

0
Jan Schultke On

Within the class body, MyContainer is still an incomplete class. This is due to the fact that classes get parsed in two passes:

  1. parse all member declarations (data members, static_assert, etc.)
  2. parse member definitions (member function bodies, etc.)

You could do

static_assert(std::is_default_constructible<MyContainer<int>>::value, "MyContainer is not default constructible!");

... outside the class, but that would only apply to a single specialization for the class template.

You could also put the static_assert in some member function of MyContainer. Within a member function, the surrounding class is complete.

0
wu1meng2 On

You can use the PImpl Pattern to separate the implementation from the interface class, and then static_assert properties of the implementation class within the interface class. In this way, the implementation class is complete when you call static_assert().

Live on Coliru

#include <experimental/propagate_const>
#include <iostream>
#include <memory>
#include <type_traits>

template <typename T>
class MyContainer {
  class impl;

  static_assert(std::is_default_constructible<MyContainer::impl>::value,
                "MyContainer is not default constructible!");

  std::experimental::propagate_const<std::unique_ptr<impl>> pImpl;
};

template <typename T>
class MyContainer<T>::impl {
 public:
  impl() = default;
};

int main() {
  // instantiate the object so that static assert may be evaluated
  MyContainer<int> obj1;

  std::cout << "All checks passed." << std::endl;

  return 0;
}