C ++ много перегрузок в стиле SFINAE - PullRequest
2 голосов
/ 31 января 2020

Можно ли сделать много перегрузок / спецификаций одновременно много раз, как в коде ниже. Надеюсь, понятно, чего я пытался добиться, но компилятор так не считает.

#include <stdint.h>

struct IP_address
{
    uint32_t value;
};


template<typename T> struct Unsigned_type {};
template<> struct Unsigned_type<uint8_t >{ typedef uint8_t   type; };
template<> struct Unsigned_type<uint16_t>{ typedef uint16_t  type; };
template<> struct Unsigned_type<uint32_t>{ typedef uint32_t  type; };
template<> struct Unsigned_type<uint64_t>{ typedef uint64_t  type; };

template<typename T> struct Signed_type {};
template<> struct Signed_type<uint8_t >{ typedef uint8_t   type; };
template<> struct Signed_type<uint16_t>{ typedef uint16_t  type; };
template<> struct Signed_type<uint32_t>{ typedef uint32_t  type; };
template<> struct Signed_type<uint64_t>{ typedef uint64_t  type; };

template<typename T> 
T parse(const char*);

template <typename T>
typename Unsigned_type<T>::type parse(const char* str)
{
    return 1;
}

template <typename T>
typename Signed_type<T>::type parse(const char* str)
{
    return -1;
}

template <>
IP_address parse(const char* str)
{
    IP_address result;
    result.value = 0x08080808;
    return result;
}


int main()
{
    uint32_t parsed_uint = parse<uint32_t>("300");
    int32_t parsed_int = parse<int32_t>("-1337");
    IP_address parsed_ip = parse<IP_address>("8.8.8.8");
    uint8_t should_throw = parse<uint8_t>("300");
    return 0;
}

И clang, и g cc говорят call to 'parse' is ambiguous, но я понятия не имею, почему, я явно указали тип! Пожалуйста, помогите мне понять, почему это не компилируется и как заставить это работать? Кстати, можно ли вообще не повторяться без использования макроса в этом случае?

Ошибка компиляции, как и было запрошено.

test.cpp: In function ‘int main()’:
test.cpp:47:49: error: call of overloaded ‘parse<uint32_t>(const char [4])’ is ambiguous
   47 |     uint32_t parsed_uint = parse<uint32_t>("300");
      |                                                 ^
test.cpp:22:3: note: candidate: ‘T parse(const char*) [with T = unsigned int]’
   22 | T parse(const char*);
      |   ^~~~~
test.cpp:25:33: note: candidate: ‘typename Unsigned_type<T>::type parse(const char*) [with T = unsigned int; typename Unsigned_type<T>::type = unsigned int]’
   25 | typename Unsigned_type<T>::type parse(const char* str)
      |                                 ^~~~~
test.cpp:31:31: note: candidate: ‘typename Signed_type<T>::type parse(const char*) [with T = unsigned int; typename Signed_type<T>::type = unsigned int]’
   31 | typename Signed_type<T>::type parse(const char* str)
      |                               ^~~~~
test.cpp:50:48: error: call of overloaded ‘parse<uint8_t>(const char [4])’ is ambiguous
   50 |     uint8_t should_throw = parse<uint8_t>("300");
      |                                                ^
test.cpp:22:3: note: candidate: ‘T parse(const char*) [with T = unsigned char]’
   22 | T parse(const char*);
      |   ^~~~~
test.cpp:25:33: note: candidate: ‘typename Unsigned_type<T>::type parse(const char*) [with T = unsigned char; typename Unsigned_type<T>::type = unsigned char]’
   25 | typename Unsigned_type<T>::type parse(const char* str)
      |                                 ^~~~~
test.cpp:31:31: note: candidate: ‘typename Signed_type<T>::type parse(const char*) [with T = unsigned char; typename Signed_type<T>::type = unsigned char]’
   31 | typename Signed_type<T>::type parse(const char* str)
      |          

Ответы [ 2 ]

0 голосов
/ 31 января 2020

Ваша проблема - проблема перегрузки, а не проблема шаблонов.

Упрощение лота дает две функции:

type1 parse(char *) {...}
type2 parse(char *) {...}

Итак, с этой стороны вызов неоднозначен. Вы пытаетесь исправить эту неоднозначность, явно предоставив параметр шаблона, но экземпляры Signed и Unsigned могут быть созданы. (Не важно, что вы явно специализировали некоторые из них).

Итак, вы получаете двусмысленность.

Как это исправить: Есть разные способы, но я выбрал std: : enable_if для отмены ненужных перегрузок:

template <typename T, typename = std::enable_if_t<!std::is_signed_v<T>>>
typename Unsigned_type<T>::type parse(const char* str)
{
    return 1;
}

template <typename T, typename = std::enable_if_t<std::is_signed_v<T>>>
typename Signed_type<T>::type parse(const char* str)
{
    return -1;
}

Здесь у вас есть полный код (не для того, чтобы было сделано больше изменений): https://godbolt.org/z/esAAR6

0 голосов
/ 31 января 2020

Вы не можете частично специализировать шаблонные функции. Но вы можете определить шаблон класса с оператором вызова SFINAE:

#include <type_traits>

template<typename T>
struct parse
{
    template<class U = T>
    std::enable_if_t<std::is_signed_v<U>, T> operator()(const char* str)
    {
        return -1;
    }

    template<class U = T>
    std::enable_if_t<!std::is_signed_v<U>, T> operator()(const char* str)
    {
        return 1;
    }
};

Когда parse<T> явно специализирован, происходит замена параметров шаблона. Если T, например, unsigned, std::is_signed_v<T> будет false. Это заставляет компилятор пытаться специализировать std::enable_if_t<false, T>, который является некорректным. К счастью, это происходит в рамках SFINAE: это определение просто игнорируется, что приводит к подбору неподписанной версии.

Немного документации

Демо

Переименование класса parse parse_impl, вы можете определить вспомогательную функцию:

template<class T>
auto parse(const char* arg)
{ return parse_impl<T>{}(arg); }

Полная программа

...