Home > Enterprise >  Create compiler error when constructor input contains duplicates
Create compiler error when constructor input contains duplicates

Time:10-02

Consider the following classes:

class Base {
public:
    Base(const std::initializer_list<const char*>& words) 
        : words_(words) {}
    std::initializer_list<const char*> words_;
};

class Derived_OK : public Base
{
public:
    Derived_OK()
        : Base({ "dog", "car", "time"}){}

};

I would like to disallow derived classes from the Base class where the initializer list contains duplicates by creating a compile time error. For example the following class should not be allowed:

class Derived_BAD : public Base
{
public:
    Derived_BAD()
        : Base({ "dog", "car", "time", "car"}){} // do not want to allow duplicates at compile time

};

My initial approach was to try templating the Base class. However, as far as I have determined I cannot use non-type template parameters, even in C 20 where a string can be passed as a parameter (I believe you can pass only one string in the C 20 approach).

My next approach was to write a constexpr function to determine if the words are unique

constexpr bool unique_words(const std::initializer_list<const char*>& words);

and then rewrite the Base class as follows:

class Base {
public:
    constexpr Base(const std::initializer_list<const char*>& words) 
        : words_(words)
    {
        static_assert(unique_words(words));
    }
    std::initializer_list<const char*> words_;
};

Although this function works outside of the class, inside of the Base constructor the compiler tells me that I cannot use the value of the constructor parameter as a constant. Of course, I could write a run time check, but I really want to discourage creating duplicate words in the initializer list at compile time. Is this possible?

CodePudding user response:

To check anything, you have to constexpr construct the class. To trigger compile-time error inside constexpr function, you could throw, see Generate compile-time error if compile-time-constant parameter is wrong .

#include <initializer_list>
#include <type_traits>
#include <array>
#include <stdexcept>

template<std::size_t N>
constexpr bool unique_words(const std::array<const char*, N>& words) {
    // TODO: implement real logic here
    return words[0][0] == 'd';
}

template<std::size_t N>
struct Base {
    constexpr Base(const std::array<const char*, N>& words) 
        : words_(words)
    {
        if (!unique_words<N>(words)) {
             throw std::invalid_argument("Base must take unique words!");
        }
    }
    std::array<const char*, N> words_;
};


struct Derived_BAD : public Base<4> {
    constexpr Derived_BAD() : Base{{"e", "car", "time", "car"}} {}
};

int main() {
    constexpr Derived_BAD var; // compile-time error - can't throw in constexpr
    Derived_BAD var2; // will throw at runtime
}

Do not store std::initializer_list in your class!! See using std::initializer_list as a member variable .

It's not possible to compile-time check when not constructing constexpr. If you really want to check such cases, you can use GNU __builtin_constant_p extension and enable compiler optimizations, see GNU documentation and Enable static checks for constant evaluation and How to get a compile time error in constant evaluated expression? .

  • Related