Приоритет при выборе перегруженных шаблонных функций в C ++ - PullRequest
15 голосов
/ 26 августа 2009

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

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

EDIT:

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

Ответы [ 6 ]

16 голосов
/ 26 августа 2009

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

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

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

7 голосов
/ 26 августа 2009

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

Если вы используете 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. Такой подход позволяет вам иметь более одного конкретного случая.

1 голос
/ 26 августа 2009

Выражение:

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

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

0 голосов
/ 10 августа 2016

Я искал для установки приоритетов на перекрывающихся 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!

0 голосов
/ 26 августа 2009

Только тип приведен к базе

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

это работает ....

...