Приоритет при выборе перегруженных шаблонных функций в 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 ((Базовый *)& производные)
оно работает....