Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define compile time ternary literal in C++?

In Chapter 19 of the 4th edition of the C++ Programming Language book, there is an example of defining a ternary number literal using a template technique, but the example does not compile. I tried to fix it in the way it looks right to me, but it still does not compile.

#include <cstdint>
#include <iostream>

using namespace std;

constexpr uint64_t ipow(uint64_t x, uint64_t n)
{
  return n > 0 ? x * ipow(x, n - 1) : 1;
}

template <char c>
constexpr uint64_t base3()
{
  static_assert(c >= '0' && c <= '2', "Not a ternary digit.");
  return c - '0';
}

template <char c, char... tail>
constexpr uint64_t base3()
{
  static_assert(c >= '0' && c <= '2', "Not a ternary digit.");
  return ipow(3, sizeof...(tail)) * (c - '0') + base3<tail...>();
}

template <char... chars>
constexpr uint64_t operator""_b3()
{
  return base3<chars...>();
}

int main()
{
  cout << 1000_b3 << endl;
  return 0;
}

Clang gives the following error:

error: call to 'base3' is ambiguous
  return ipow(3, sizeof...(tail)) * (c - '0') + base3<tail...>();
                                                ^~~~~~~~~~~~~~
<source>:22:49: note: in instantiation of function template specialization 'base3<'0', '0'>' requested here
<source>:22:49: note: in instantiation of function template specialization 'base3<'0', '0', '0'>' requested here
<source>:28:10: note: in instantiation of function template specialization 'base3<'1', '0', '0', '0'>' requested here
  return base3<chars...>();
         ^
<source>:33:15: note: in instantiation of function template specialization 'operator""_b3<'1', '0', '0', '0'>' requested here
  cout << 1000_b3 << endl;
              ^
<source>:12:20: note: candidate function [with c = '0']
constexpr uint64_t base3()
                   ^
<source>:19:20: note: candidate function [with c = '0', tail = <>]
constexpr uint64_t base3()
                   ^
1 error generated.

What is the right way to define it?

like image 260
bobeff Avatar asked Nov 01 '25 10:11

bobeff


1 Answers

Currently, when tail only has 1 character (when called with the last digit '0' of your user defined literal), it could call either overload of base3

template <char c>
constexpr uint64_t base3()  // With c as '0'
template <char c, char... tail>
constexpr uint64_t base3()  // With c as '0' and tail as an empty parameter pack

There is no reason to prefer one over the other, so it is ambiguous.

You need the second overload to not work with exactly 1 argument, so you can make sure it takes at least 2 arguments:

template <char c, char second, char... tail>
constexpr uint64_t base3()
{
  static_assert(c >= '0' && c <= '2', "Not a ternary digit.");
  return ipow(3, 1+sizeof...(tail)) * (c - '0') + base3<second, tail...>();
}

Or do it with SFINAE:

template <char c, char... tail>
constexpr
typename std::enable_if<sizeof...(tail) != 0, uint64_t>::type
base3()
{
  static_assert(c >= '0' && c <= '2', "Not a ternary digit.");
  return ipow(3, sizeof...(tail)) * (c - '0') + base3<tail...>();
}

Or change it to a base case of 0 characters:

template <typename = void>  // Can be called with an empty set of template args
constexpr uint64_t base3()
{
  return 0;
}

template <char c, char... tail>
constexpr uint64_t base3()
{
  static_assert(c >= '0' && c <= '2', "Not a ternary digit.");
  return ipow(3, sizeof...(tail)) * (c - '0') + base3<tail...>();
}
like image 120
Artyer Avatar answered Nov 04 '25 02:11

Artyer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!