Перегруженные функции неоднозначны при использовании принципа SFINAE - PullRequest
4 голосов
/ 02 февраля 2011

Я наткнулся на некоторый код, написанный на VS7.1, и теперь я пытаюсь заставить его работать на MacOSX.Я понимаю, что фрагмент кода ниже о SFINAE принципе .Из того, что я понимаю, код используется во время компиляции, чтобы знать, что это за тип, полагаясь на некоторую магию создания шаблона.Короче говоря, правильная перегрузка определяется путем просмотра аргумента шаблона.

Вот код, который у меня есть.Несколько упрощенно, чтобы показать только проблему.

template <typename T>
struct SomeClass
{
};

template <>
struct SomeClass<char>
{
    typedef char Type;
};

template <typename T>
struct IsChar
{
    typedef char Yes;
    typedef int No;

    template <typename U>
    static Yes Select(U*, typename SomeClass<U>::Type* p = 0);
    template <typename U>
    static No Select(U*, ...);
    static T* MakeT();

    const static bool Value = sizeof(Select(MakeT())) == sizeof(Yes);
};

Я просто использую это так:

if (IsChar<int>::Value)
{
    ...

При компиляции приведенный выше код хорошо работает , и этовыбирает самый верхний класс из-за отсутствия typedef для Type при использовании int.

Если я вместо этого теперь использую char ...

if (IsChar<char>::Value)
{
    ...

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

Код работал нормально по крайней мере на VS7.1, но не на gcc для MacOSX и не на gcc4.4 для Linux.

Anyпредложения как это исправить?Может быть, это обычно делается по-другому?

Спасибо!

ОБНОВЛЕНИЕ: я понял, что пример кода, который я дал, может быть немного слишком упрощен, потому что я считаю, что мы не проверяем jsut на типздесь, даже если я по ошибке заставляю это выглядеть так.Я должен буду собрать немного больше информации для вас сегодня вечером, потому что у меня нет кода здесь.Извините за это.

ОБНОВЛЕНИЕ2: Даже если моя презентация плохая, и это из-за того, что вы не знакомы с оригинальным кодом или не используете шаблоны таким образом.Тем временем я собираю немного больше информации, давайте предположим, что эти конструкции существуют по какой-то причине, и все имена, которые я дал, неверны, как насчет проблемы с компилятором?Почему он не может выбрать правильную перегруженную функцию здесь?Это тоже мне интересно.Как я уже сказал, я вернусь с более подробным объяснением общей цели.

Редактировать

После более внимательного изучения исходного кода он используетboost :: integra_constant, а также boost :: enable_if, как было предложено здесь.Проблема связана с тем, как выводятся аргументы шаблона, и это не сработало так, как было настроено.Однако, следуя тому, что Георг предложил в конце своего ответа, я мог исправить вещи, чтобы принять вещи.У меня сейчас следующее:

typedef char Yes;
typedef int No;

template <typename U> static Yes Select(typename SomeClass<U>::Type* p);
template <typename U> static No Select(...);

static const bool Value = sizeof(Select<T>(0)) == sizeof(Yes);

Это хорошо работает.Немного поэкспериментировав, я обнаружил, что наличие двух параметров функции в функциях выбора приводит к проблеме.Я не нашел причину.Я вернусь к этому, когда пойму лучше.

Спасибо за вашу помощь.По крайней мере, теперь я понимаю принципы и то, как все должно работать.Только некоторые детали, которые пока неизвестны.

Ответы [ 2 ]

9 голосов
/ 02 февраля 2011

Если я не понимаю намерения, приведенный выше пример использования не требует использования SFINAE. Если вы хотите статически утверждать тип Type, вы можете просто использовать что-то вроде этого:

template<class T1, class T2> struct SameType {
    static const bool Value = false;
};

template<class T> struct SameType<T, T> {
    static const bool Value = true;
};

template <typename T>
struct IsChar {
    static const bool Value = SameType<T, char>::Value;
};

Если вам действительно нужно использовать это для SFINAE (т.е. отключение / включение функций на основе параметров шаблона), просто используйте вышеупомянутое в комбинации с чем-то вроде Boosts enable_if:

template<class T> 
typename boost::enable_if_c<IsChar<T>::Value, void>::type
someFunction() {
}

Или, если вы можете пройти Boost до конца:

template<class T> 
typename boost::enable_if<boost::mpl::is_same<T, char>, void>::type
someFunction() {
}

Обновление

Если перечитать, если вы действительно хотите проверить, имеет ли специализация SomeClass typedef Type, вы можете использовать решение с здесь :

template<class T> struct HasType {
    template<class U> static char (&test(typename U::Type const*))[1];
    template<class U> static char (&test(...))[2];
    static const bool Value = (sizeof(test< SomeClass<T> >(0)) == 1);
};

В этом случае IsChar, безусловно, является неправильным, хотя что-то вроде HasType или HasTypedefType будет более наглядным:)

6 голосов
/ 02 февраля 2011

В своем комментарии я сказал, что вы обычно не используете результаты этих предикатов в качестве значений для if, и вот почему:

// assume we have has_typedef_type from the Wikipedia page:

template <typename T>
void foo(const T& x)
{
    if (has_typedef_type<T>::value)
    {
        // the predicate is true, so T::type exists
        typename T::type y = x;
    }
    else
    {
        // the predicate is false, so T::type doesn't exist, do something else
        float y = x;
    }
}

Это может выглядеть хорошо, но учтите, что когда предикат имеет значение false, компилятор попытается скомпилировать это:

// let's say we called it with double
void foo<double>(const double& x)
{
    if (false)
    {
        // wait, double doesn't have this!
        typename double::type y = x;
    }
    else
    {
        float y = x;
    }
}

Проблема в том, что код, даже если он будет удален с удалением мертвого кода, является некорректным. Решение состоит в том, чтобы сделать также if время компиляции, но сначала немного котла:

// in C++0x, these are defined in <type_traits>, but we'll do it ourselves
// (Boost has these as well)
typename <typename T, T Value>
struct integral_constant
{
    typedef T type;
    static const T value = Value;
};

typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;

На этом пути мы определяем наши функции:

namespace detail
{
    // here are the real implementations
    template <typename T>
    void foo(const T& x, true_type)
    {
        // the predicate is true, so T::type exists
        typename T::type y = x;
    }

    template <typename T>
    void foo(const T& x, false_type)
    {
        // the predicate is false, so T::type doesn't exist, do something else
        float y = x;
    }
}

template <typename T>
void foo(const T& x)
{
    detail::foo(x, // chose which function to call, using the type of this:
                integral_constant<bool, has_typedef_type<T>::value>());
}

Теперь все в порядке, потому что ветви полностью независимы друг от друга.

...