Search code examples
c++c++17template-meta-programmingstatic-assertstdinitializerlist

How to `static_assert` the construction of a template class in member initializer list?


I have MyClass which is a template class. I wanted to provide an initialize r list constructor so that I can conveniently write:

MyClass<int> Arr0{ 1,  2,  3, 4, 5, 8 };

On the other hand, I do not want to have duplicates in this list as this class meant to have only unique user inputs. I have seen many ways to check the duplicates in the array and I came up with the has_duplicates() following function.

I tried to combine the idea of checking, whether the std::initializer_list<T>ed temporary elements(or array) contains any duplicate elements in the member initializer list itself; if it contains static_assert() the template instantiation and thereby no object of this class will be constructed.

Following is the minimal example of my code.

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <iterator>
#include <initializer_list>

template <typename Iterator> // function to check duplicates(which works fine)
constexpr bool has_duplicates(Iterator start, Iterator end)
{
    if (start == end) return false;
    using Type = typename std::remove_reference_t<decltype(*end)>;
    std::map<Type, std::size_t> countMap;
    for (; start != end; ++start)
    {
        countMap[*start]++;
        if (countMap[*start] >= 2) return true;
    }
    return false;
}

template <typename T> class MyClass
{
private:
    std::vector<T> m_vec;

public:
    MyClass(std::initializer_list<T> a)
        : (has_duplicates(a.begin(), a.end()) //-----> here is the problem
            ? static_assert(false, " the array has duplicates....")
            : m_vec(a)
           )
    {
        std::cout << "Constriction successful....";
    }
};

int main()
{
    std::vector<int> test{ 1, 2, 3, 4, 1 };
    std::cout << std::boolalpha 
        << has_duplicates(test.begin(), test.end()) << std::endl; // works
    MyClass<int> Arr0{ 1,  2,  3, 4 }; // error
    return 0;
}

Upon compiling in MSVC 16.0(C++17 flag), this gives me the error:

error C2059: syntax error: 'static_assert'
note: while compiling class template member function 'MyClass<int>::MyClass(std::initializer_list<_Ty>)'
      with
      [
          _Ty=int
      ]
note: see reference to function template instantiation 'MyClass<int>::MyClass(std::initializer_list<_Ty>)' being compiled
      with
      [
          _Ty=int
      ]
note: see reference to class template instantiation 'MyClass<int>' being compiled
error C2143: syntax error: missing ';' before '}'
error C2059: syntax error: ')'
error C2447: '{': missing function header (old-style formal list?)

It says an simple syntax error, but I do not see any as per static_assert.

Can anybody help me find out the error?

What is the correct way to prevent construction of std::initializer_list<T> constutor arguments, in the above case?


Solution

  • What you're trying to do, a static assert to check the arguments of a constructor, is (as far I know) simply impossible.

    A static_assert() works compile time where MyClass object is initialized (can be initialized) run-time.

    The best I can imagine is a make_MyClass() function that receive a list of arguments as template parameters

    template <auto v0, auto ... vs>
    auto make_MyClass ()
     {
       static_assert( false == has_duplicates<v0, vs...>() );
    
       return MyClass<decltype(v0)>{ v0, vs... };
     }
    

    so you can perform a static_assert() because now you know compile time the values; I've rewritten the has_duplicates() function as follows because your original function can't be effectively constexpr (because std::map isn't)

    template <typename = void>
    constexpr bool has_duplicates ()
     { return false; }
    
    template <auto v0, auto ... vs>
    constexpr bool has_duplicates ()
     { return ((v0 == vs) || ... ) || has_duplicates<vs...>(); }
    

    The following is a full compiling example

    #include <iostream>
    #include <vector>
    #include <initializer_list>
    
    template <typename = void>
    constexpr bool has_duplicates ()
     { return false; }
    
    
    template <auto v0, auto ... vs>
    constexpr bool has_duplicates ()
     { return ((v0 == vs) || ... ) || has_duplicates<vs...>(); }  
    
    template <typename T> class MyClass
    {
    private:
        std::vector<T> m_vec;
    
    public:
        MyClass(std::initializer_list<T> a) : m_vec{a}
         { std::cout << "Constriction successful...."; }
    };
    
    template <auto v0, auto ... vs>
    auto make_MyClass ()
     {
       static_assert( false == has_duplicates<v0, vs...>() );
    
       return MyClass<decltype(v0)>{ v0, vs... };
     }
    
    int main ()
     {
        std::cout << std::boolalpha 
            << has_duplicates<1, 2, 3, 4, 1>() << std::endl;
    
        auto mc0 = make_MyClass<1, 2, 3, 4, 5>(); // compile
        //auto mc1 = make_MyClass<1, 2, 3, 4, 1>(); // static_assert error
     }