Write a function that only accepts literal `0` or literal `1` as argument

You can get this by passing the 0 or 1 as a template argument like so:

template <int value, typename = std::enable_if_t<value == 0 | value == 1>>
void f() {
    // Do something with value
}

The function would then be called like: f<0>(). I don't believe the same thing can be done for constructors (because you can't explicitly set template parameters for constructors), but you could make the constructor(s) private and have static wrapper functions which can be given template parameters perform the check:

class A {
private:
    A(int value) { ... }

public:
    template <int value, typename = std::enable_if_t<value == 0 || value == 1>>
    static A make_A() {
        return A(value);
    }
};

Objects of type A would be created with A::make_A<0>().


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.


Well... you have tagged C++17, so you can use if constexpr.

So you can define a literal type when 0_x is a std::integral_constant<int, 0> value, when 1_x is a std::integral_constant<int, 1> and when 2_x (and other values) gives a compilation error.

By example

template <char ... Chs>
auto operator "" _x()
 {
   using t0 = std::integer_sequence<char, '0'>;
   using t1 = std::integer_sequence<char, '1'>;
   using tx = std::integer_sequence<char, Chs...>;

   if constexpr ( std::is_same_v<t0, tx> )
      return std::integral_constant<int, 0>{};
   else if constexpr ( std::is_same_v<t1, tx> )
      return std::integral_constant<int, 1>{};
 }

int main ()
 {
   auto x0 = 0_x;
   auto x1 = 1_x;
   //auto x2 = 2_x; // compilation error

   static_assert( std::is_same_v<decltype(x0),
                                 std::integral_constant<int, 0>> );
   static_assert( std::is_same_v<decltype(x1),
                                 std::integral_constant<int, 1>> );
 }

Now your f() function can be

template <int X, std::enable_if_t<(X == 0) || (X == 1), bool> = true>
void f (std::integral_constant<int, X> const &)
 {
   // do something with X
 }

and you can call it as follows

f(0_x);
f(1_x);