Приоритет при выборе перегруженных функций шаблона в С++
У меня есть следующая проблема:
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!