Sometimes for algebraic types it is convenient to have a constructor that takes a literal value 0
to denote the neutral element, or 1
to denote the multiplicative identity element, even if the underlying type is not an integer.
The problem is that it is not obvious how to convince the compiler only to accept, 0
or 1
without accepting any other integer.
Is there a way to do this in C++14 or beyond, for example combining literals, constexpr or static_assert?
Let me illustrate with a free function (although the idea is to use the technique for a constructor that take a single argument. Contructors cannot take template parameters either).
A function that accepts zero only could be written in this way:
constexpr void f_zero(int zero){assert(zero==0); ...}
The problem is that, this could only fail at runtime. I could write f_zero(2)
or even f_zero(2.2)
and the program will still compile.
The second case is easy to remove, by using enable_if
for example
template<class Int, typename = std::enable_if_t<std::is_same<Int, int>{}> >
constexpr void g_zero(Int zero){assert(zero==0);}
This still has the problem that I can pass any integer (and it only fails in debug mode).
In C++ pre 11 one had the ability to do this trick to only accept a literal zero.
struct zero_tag_{};
using zero_t = zero_tag_***;
constexpr void h_zero(zero_t zero){assert(zero==nullptr);}
This actually allowed one to be 99% there, except for very ugly error messages.
Because, basically (modulo Maquevelian use), the only argument accepted would be h_zero(0)
.
This is situation of affairs is illustrated here https://godbolt.org/z/wSD9ri . I saw this technique being used in the Boost.Units library.
1) Can one do better now using new features of C++?
The reason I ask is because with the literal 1
the above technique fails completely.
2) Is there an equivalent trick that can be applied to the literal 1
case? (ideally as a separate function).
I could imagine that one can invent a non-standard long long literal _c
that creates an instance of std::integral_constant<int, 0>
or std::integral_constant<int, 1>
and then make the function take these types. However the resulting syntax will be worst for the 0
case. Perhaps there is something simpler.
f(0_c);
f(1_c);
EDIT: I should have mentioned that since f(0)
and f(1)
are potentially completely separate functions then ideally they should call different functions (or overloads).
In C++20 you can use the consteval
keyword to force compile time evaluation. With that you could create a struct, which has a consteval
constructor and use that as an argument to a function. Like this:
struct S
{
private:
int x;
public:
S() = delete;
consteval S(int _x)
: x(_x)
{
if (x != 0 && x != 1)
{
// this will trigger a compile error,
// because the allocation is never deleted
// static_assert(_x == 0 || _x == 1); didn't work...
new int{0};
}
}
int get_x() const noexcept
{
return x;
}
};
void func(S s)
{
// use s.get_x() to decide control flow
}
int main()
{
func(0); // this works
func(1); // this also works
func(2); // this is a compile error
}
Here's a godbolt example as well.
Edit:
Apperently clang 10
does not give an error as seen here, but clang (trunk)
on godbolt does.