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

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

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(...) для достижения этой цели?

РЕДАКТИРОВАТЬ:

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

6 ответов

Решение

Я нашел ОЧЕНЬ простое решение!

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);
  }
};

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

Вы должны использовать 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;
}

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

Я хотел расставить приоритеты для assign, если он существовал, иначе использовал итератор вставки. Это общий пример того, что я придумал (модифицированный с бесконечными уровнями приоритета некоторыми удобными людьми в канале #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;
}

Было также предложено, чтобы в C++ 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-balloon @ #boost!

Если вы используете 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);
    }
};

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

Выражение:

X::func(derived)

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

static const char *func(Derived *data);

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

static const char *func(Base *data);

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

Я предлагаю использовать специализацию в 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";
  }

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

Просто тип, выведенный на базу

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

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

Другие вопросы по тегам