Можно ли проверить, задан ли пользовательский литерал для заданного типа и аргумента?

Я хочу проверить во время компиляции, если пользовательский литерал _name определен для типа Ret и аргумента Arg. Хотя у меня есть половинное решение, для этого требуется, чтобы литерал operator определялся хотя бы один раз:

#include <iostream>
#include <type_traits>

struct one { };
struct two { };

// we need at least one of these definitions for template below to compile
one operator"" _x(char const*) {return {};}
two operator"" _x(unsigned long long int) {return {};}

template<class T, class S, class = void>
struct has_literal_x : std::false_type
{  };

template<class T, class S>
struct has_literal_x <T, S,
    std::void_t<decltype((T(*)(S))(operator"" _x))>
    > : std::true_type
{ };

int main()
{
    std::cout << has_literal_x<one, char const*>::value << std::endl;
    std::cout << has_literal_x<two, unsigned long long int>::value << std::endl;

    std::cout << has_literal_x<one, unsigned long long int>::value << std::endl;
    std::cout << has_literal_x<two, char const*>::value << std::endl;

    std::cout << has_literal_x<int, char const*>::value << std::endl;
}

Вывод:

1
1
0
0
0

Но если нет хотя бы одного определения, возможно, перегруженного пользовательского литерала, это решение не будет работать. Есть ли способ проверить это даже на несуществующие литералы (возможно, так же, как мы можем проверить, имеет ли класс X член member, но я не знаю, насколько он жизнеспособен в этом случае)?

Ответ 1

Можно ли проверить, является ли пользовательский литерал заданным для данного типа и аргумента?

Ответ (короткий) да.


В качестве примера вы можете использовать следующую специализацию в вашем примере кода:

template<class T, class S> 
struct has_literal_x <T, S,
      std::enable_if_t<std::is_same<decltype(operator""_x(std::declval<S>())), T>::value>
    > : std::true_type
{ };

Это быстро становится:

#include <iostream>
#include <type_traits>
#include <utility>

struct one { };
struct two { };

//one operator"" _x(char const*) { return {}; }
//two operator"" _x(unsigned long long int) { return {}; }

template<class T, class S, class = void>
struct has_literal_x : std::false_type
{  };

template<class T, class S> 
struct has_literal_x <T, S, 
      std::enable_if_t<std::is_same<decltype(operator""_x(std::declval<S>())), T>::value> 
    > : std::true_type
{ };

int main()
{  
    std::cout << has_literal_x<one, char const*>::value << std::endl;
    std::cout << has_literal_x<two, unsigned long long int>::value << std::endl;

    std::cout << has_literal_x<one, unsigned long long int>::value << std::endl;
    std::cout << has_literal_x<two, char const*>::value << std::endl;

    std::cout << has_literal_x<int, char const*>::value << std::endl;
}

Результат - ожидаемый: 0 для всех из них.


Другой способ сделать это в С++ 14 (главным образом, вдохновленный этим ответом @Jarod42) с помощью переменной шаблона.
В качестве примера:

template<typename T, typename S, typename = void>
constexpr bool has_literal_v = false;

template<typename T, typename S>
constexpr bool has_literal_v<T, S, std::enable_if_t<std::is_same<decltype(operator""_x(std::declval<S>())), T>::value>> = true;

Вместо этого будет main:

int main()
{  
    std::cout << has_literal_v<one, char const*> << std::endl;
    std::cout << has_literal_v<two, unsigned long long int> << std::endl;

    std::cout << has_literal_v<one, unsigned long long int> << std::endl;
    std::cout << has_literal_v<two, char const*> << std::endl;

    std::cout << has_literal_v<int, char const*> << std::endl;
}

Мне легко читать и что переменная constexpr. Что еще?

Ответ 2

С семейством функций is_detected вы можете просто сделать

template <typename T>
using has_literal_x_type = decltype(operator"" _x(std::declval<T>()));

template <typename Ret, typename T>
using has_literal_x = std::is_same<Ret, detected_t<has_literal_x_type, T>>;

И протестируйте его с помощью

static_assert(!has_literal_x<one, char const*>::value, "unexpected");
static_assert(!has_literal_x<one, unsigned long long int>::value, "unexpected");
static_assert(!has_literal_x<two, char const*>::value, "unexpected");
static_assert(!has_literal_x<two, unsigned long long int>::value, "unexpected");
static_assert(!has_literal_x<int, char const*>::value, "unexpected");

Демо