Приоритет при выборе перегруженных функций шаблона в С++

У меня есть следующая проблема:

class Base
{
};

class Derived : public Base
{
};

class Different
{
};

class X
{
public:
  template <typename T>
  static const char *func(T *data)
  {
    // Do something generic...
    return "Generic";
  }

  static const char *func(Base *data)
  {
    // Do something specific...
    return "Specific";
  }
};

Если я сейчас сделаю

Derived derived;
Different different;
std::cout << "Derived: " << X::func(&derived) << std::endl;
std::cout << "Different: " << X::func(&different) << std::endl;

Я получаю

Derived: Generic
Different: Generic

Но я хочу, чтобы для всех классов, полученных из Base, вызывается конкретный метод. Таким образом, результат должен быть:

Derived: Specific
Different: Generic

Можно ли каким-либо образом переделать X: func (...) s для достижения этой цели?

EDIT:

Предположим, что вызывающий объект X:: func (...) не известен, если класс, представленный как параметр, получен из Base или нет. Таким образом, Casting to Base не является вариантом. На самом деле идея всего заключается в том, что X:: func (...) должен "обнаруживать", если параметр получен из Base или нет и вызывает другой код. И по соображениям производительности "обнаружение" должно быть сделано во время компиляции.

Ответы

Ответ 1

Я нашел ОЧЕНЬ легкое решение!

class Base
{
};

class Derived : public Base
{
};

class Different
{
};

class X
{
private:
  template <typename T>
  static const char *intFunc(const void *, T *data)
  {
    // Do something generic...
    return "Generic";
  }

  template <typename T>
  static const char *intFunc(const Base *, T *data)
  {
    // Do something specific...
    return "Specific";
  }

public:
  template <typename T>
  static const char *func(T *data)
  {
    return intFunc(data, data);
  }
};

Это отлично работает и очень тонкое! Хитрость заключается в том, чтобы позволить компилятору выбрать правильный метод с помощью (иначе бесполезного) первого параметра.

Ответ 2

Для этого вы должны использовать SFINAE. В следующем коде первая функция может быть инстанцирована тогда и только тогда, когда вы передаете то, что не может быть (неявно) преобразовано в Base *. Вторая функция имеет это обратное.

Возможно, вы захотите прочитать enable_if.

#include <iostream>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>

class Base {};
class Derived : public Base {};
class Different {};

struct X
{
    template <typename T>
    static typename boost::disable_if<boost::is_convertible<T *, Base *>,
        const char *>::type func(T *data)
    {
        return "Generic";
    }

    template <typename T>
    static typename boost::enable_if<boost::is_convertible<T *, Base *>,
        const char *>::type func(T *data)
    {
        return "Specific";
    }
};

int main()
{
    Derived derived;
    Different different;
    std::cout << "Derived: " << X::func(&derived) << std::endl;
    std::cout << "Different: " << X::func(&different) << std::endl;
}

Ответ 3

Выражение:

X::func(derived)

означает, что компилятор будет генерировать декларацию и код, которые эффективно имеют эту подпись:

static const char *func(Derived *data);

который окажется лучше, чем ваш:

static const char *func(Base *data);

Функция шаблона будет использоваться для всех, что является законным для typename, например. любой класс, который вы используете как T, и он эффективно исключает использование функции Base вашей функции из-за политики времени компиляции.

Мое предложение состоит в том, чтобы использовать specialization в X для ваших конкретных типов, то есть:

template <typename T>
  static const char *func(T *data)
  {
    // Do something generic...
    return "Generic";
  }

template <>
  static const char *func(Derived *data) // 'Derived' is the specific type
  {
    // Do something specific...
    return "Specific";
  }

Надеюсь, что это работает!

Ответ 4

Если вы используете boost, вы можете сделать это с помощью метапрограммирования шаблонов:

#include <boost/type_traits/is_base_of.hpp>

class X
{
private:
    template <typename T>
    static const char *generic_func(T *data)
    {
        // Do something generic...
        return "Generic";
    }

    template <typename T>
    static const char *base_func(T *data)
    {
        // Do something specific...
        return "Specific";
    }

public:
    template <typename T>
    static const char* func(T* data)
    {
        if (boost::is_base_of<Base, T>::value)
            return base_func(data);

        return generic_func(data);
    }
};

Metafunction is_base_of оценивается во время компиляции, и оптимизатор, скорее всего, удалит мертвую ветвь if в функции func. Этот подход позволяет вам иметь более одного конкретного случая.

Ответ 5

Просто тип выводится на базу

Х:: FUNC ((Базовый *) & производные)

он работает....

Ответ 6

Я искал настройки приоритетов при перекрытии enable_if, особенно для отказа от вызова методов контейнера STL, где мои черты были такими, как is_assignable is_insterable и т.д., с которыми накладывается несколько контейнеров.

Я хотел назначить приоритет присваивания, если он существует, иначе используйте итератор вставки. Это общий пример того, с чем я столкнулся (измененный с бесконечным уровнем приоритета некоторыми удобными людьми в канале #boost irc). Он работает, поскольку неявное преобразование уровня приоритета оценивает перегрузку ниже другой, которая в остальном является одинаково допустимым вариантом - устранение двусмысленности.

#include <iostream>
#include <string>

template <std::size_t N>
struct priority : priority<N - 1> {};

template <>
struct priority<0> {};

using priority_tag = priority<2>;

template <typename T> 
void somefunc(T x, priority<0>)
{
    std::cout << "Any" << std::endl;
}

template <typename T> 
std::enable_if_t<std::is_pod<T>::value >
somefunc(T x, priority<2>)
{
    std::cout << "is_pod" << std::endl;
}

template <typename T>
std::enable_if_t<std::is_floating_point<T>::value >
somefunc(T x, priority<1>)
{
    std::cout << "is_float" << std::endl;
}

int main()
{
    float x = 1;
    somefunc(x, priority_tag{});
    int y = 1;
    somefunc(y, priority_tag{}); 
    std::string z;
    somefunc(z, priority_tag{});
    return 0;
}

Также было высказано предположение, что в С++ 14 я мог бы просто использовать выражения constexpr if для достижения того же, что было намного чище, если Visual Studio 2015 их поддерживала. Надеюсь, это поможет кому-то другому.

#include <iostream>
#include <string>

template <typename T>
void somefunc(T x)
{
    if constexpr(std::is_floating_point<T>::value) {
      static_assert(std::is_floating_point<T>::value);
      std::cout << "is_float" << std::endl;
    } else if constexpr(std::is_pod<T>::value) {
      static_assert(std::is_pod<T>::value);
      std::cout << "is_pod" << std::endl;
    } else {
      static_assert(!std::is_floating_point<T>::value);
      static_assert(!std::is_pod<T>::value);
      std::cout << "Any" << std::endl;
    }
}

int main()
{
    float x = 1;
    somefunc(x);
    int y = 1;
    somefunc(y); 
    std::string z;
    somefunc(z);
    return 0;
}

//благодаря k-ballo @#boost!