Search code examples
c++c++11templatesdefault-constructormost-vexing-parse

Disable construction of non fully specialized template class


I have this kind of structure for a template class.

My aim is to forbid creation of those class which not provide a full specialization:

class AbstractContainer
{
public:
   virtual ~AbstractContainer() = default;

   // declare here interface methods
};

template <class T>
class Container final : public AbstractContainer
{
public:
   Container() = delete;
};


template <>
class Container<int> : public AbstractContainer
{
public:
   Container() = default;

   explicit Container(const int& type) : AbstractContainer(), type_(type){}

private:
   int type_;
};

Everyhthing works fine

Container<int> a;     // it works
Container<int> a(5);  // it works
Container<char> a;    // does not compile

but I noticed it compiles for these case

Container<int> a(Container<char>());
Container<int> a(Container<CustomClass>());

How can I avoid this situation? I do want a copy constructor but not with the wrong type, ideally I would like to have the same issue of compile error (I could use an assert somewhere, but don't know how to set up it).


Solution

  • This seems to be a C++'s most vexing parse issue:

    Container<int> a(Container<char>());
    

    This is actually just a function declaration: It declares a function a that returns a Container<int> object and takes a pointer to a function that returns Container<char> and takes nothing.

    Since it is just a function declaration, it does not instantiate Container<char> at all and, therefore you are not getting any error.

    You could use brace syntax instead of parenthesis, i.e.:

    Container<int> a{Container<char>()};
    Container<int> b(Container<char>{});
    Container<int> c{Container<char>{}};
    

    The code above does declare three objects: a, b and c.


    Getting a custom error when instantiating Container<>'s primary template

    You could define the following template, deferred_false, as:

    template<typename>
    struct deferred_false {
        static constexpr auto value = false;
    };
    

    or simply as:

    #include <type_traits>
    template<typename> struct deferred_false: std::false_type{};
    

    Then, place a static_assert that uses this deferred_false<T>::value in the definition of your class template :

    template<class T>
    class Container final : public AbstractContainer
    {
       static_assert(deferred_false<T>::value, "Trying to instantiate Container<>");
    };
    

    This way, the assertion will fail at compile time when the primary template of Container is going to be instantiated, but won't fail when the primary template is not being instantiated, since the condition of the static_assert depends on the template parameter (i.e.: T).

    That is, if there is no specialization of Container for Foo and you have:

    Container<Foo> a;
    

    You will get the following compile-time error:

    error: static assertion failed: Trying to instantiate Container<>