Reputation: 67
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
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
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