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?
CodePudding user response:
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...>();
}