Carlos Brito
Carlos Brito

Reputation: 67

Determine uniqueness of parameters at compile time

The problem

I have the following array like container:

template <
    class _Tp,
    size_t _Size
> class Alphabet {
public:
    template <class... _Ts>
    constexpr
    Alphabet(_Ts&& ... ts) 
        : m_data(std::forward<_Ts>(ts)...})
    {}
private:
    _Tp m_data[_Size ? _Size : 1];
}

which I use like this:

constexpr Alphabet<char, 3> abc{'a', 'b', 'c'}; // ok, elements are distinct

However, I would like to be able to tell at compile time if the elements are unique. So in the body of the constructor I would just simply add:

Alphabet(_Ts&& ... ts) 
    : m_data{std::forward<_Ts>(ts)...}
{
    static_assert(is_unique(ts...), "Elements must be distinct!")
}

So now when I write:

constexpr Alphabet<char, 3> abc{'a', 'b', 'b'}; // error! elements are not distinct

What I have attempted

For this I have written the following code, which works at runtime:

template <class _Tp>
constexpr
bool is_one_of(_Tp)
{
    return false;
}

template <class _Tp, class... _Ts>
constexpr
bool is_one_of(_Tp t, _Tp f, _Ts... ts)
{
    return (t == f) || is_one_of(f, ts...);
}

template <class _Tp>
constexpr
bool is_unique(_Tp)
{
    return true;
}

template <class _Tp, class... _Ts>
constexpr
bool is_unique(_Tp t, _Ts... ts)
{   
    return is_unique(ts...) && !is_one_of(t, ts...);
}

Unfortunately, when attempting to compile I am soon met with the following errors:

error: non-constant condition for static assertion
     static_assert(detail::is_unique(ts...),
                                     ^
error: 'ts#0' is not a constant expression

I am on C++14. Any help is appreciated!

Upvotes: 1

Views: 333

Answers (1)

max66
max66

Reputation: 66200

If you want to check the ts... values compile time (static_assert()) you have to pass they as values that are ever compile-time known.

You can't check compile-time the arguments for a constexpr constructors because the constructor can be used also with run-time values.

My suggestion is to pass the ts... values as template parameter, so they are ever known compile time, so you can check them in a static_assert() test.

By example: if Tp is an integer type, you can pass them as arguments of a std::integer_sequence

  template <Tp ... Ts>
  constexpr Alphabet (std::integer_sequence<Tp, Ts...> const &)
     : m_data{Ts...}
   { static_assert(    sizeof...(Ts) <= Size
                    && are_unique<Ts...>(), "!" ); }

Regarding are_unique(), I propose a recursive version

  // ground case
  template <typename = void>
  static constexpr bool are_unique ()
   { return true; }

  // recursive case
  template <Tp T0, Tp ... Ts>
  static constexpr bool are_unique ()
   { return is_unique<T0, Ts...>() && are_unique<Ts...>(); }

and regarding is_unique(), you can use the trick of the initialization of an unused array

  template <Tp T0, Tp ... Ts>
  static constexpr bool is_unique ()
   {
     using unused = bool[];

     bool ret = true;

     (void)unused{ false, ret &= T0 != Ts... };

     return ret;
   }

If you don't like, every time, to write the std::integer_sequence part, you can make it only one time in a make_Alphabet() template function

template <typename Tp, Tp ... Ts>
constexpr Alphabet<Tp, sizeof...(Ts)> make_Alphabet ()
 { return { std::integer_sequence<Tp, Ts...>{} }; }

and use it as follows

constexpr auto abcd{ make_Alphabet<char, 'a', 'b', 'c', 'd'>() };

The following is a full compiling C++14 example

#include <utility>
#include <iostream>

template <typename Tp, std::size_t Size>
class Alphabet
 {
   private:
      Tp m_data[Size ? Size : 1U];

      template <Tp T0, Tp ... Ts>
      static constexpr bool is_unique ()
       {
         using unused = bool[];

         bool ret = true;

         (void)unused{ false, ret &= T0 != Ts... };

         return ret;
       }

      // ground case
      template <typename = void>
      static constexpr bool are_unique ()
       { return true; }

      // recursive case
      template <Tp T0, Tp ... Ts>
      static constexpr bool are_unique ()
       { return is_unique<T0, Ts...>() && are_unique<Ts...>(); }

   public:
      template <Tp ... Ts>
      constexpr Alphabet (std::integer_sequence<Tp, Ts...> const &)
         : m_data{Ts...}
       { static_assert(    sizeof...(Ts) <= Size
                        && are_unique<Ts...>(), "!" ); }
 };

template <typename Tp, Tp ... Ts>
constexpr Alphabet<Tp, sizeof...(Ts)> make_Alphabet ()
 { return { std::integer_sequence<Tp, Ts...>{} }; }

int main()
 {
   // compilation error (static_assert failed "!")
   //constexpr Alphabet<char, 3>
   //   aba{std::integer_sequence<char, 'a', 'b', 'a'>{}};

   // compile
   constexpr Alphabet<char, 3>
      abc{std::integer_sequence<char, 'a', 'b', 'c'>{}};

   // compilation error (static_assert failed "!")
   //constexpr auto abca{ make_Alphabet<char, 'a', 'b', 'c', 'a'>() };

   // compile
   constexpr auto abcd{ make_Alphabet<char, 'a', 'b', 'c', 'd'>() };
 }

Upvotes: 2

Related Questions