Резервный вариант по умолчанию для функций шаблона C++ с использованием enable_if
Я хочу написать механизм C++, в котором вызываются разные экземпляры функции, если заданный класс
Param
происходит от определенного базового класса.
Это очень хорошо работает с
std::is_base_of
а также
std::enable_if
.
Тем не менее, я хотел бы иметь "версию по умолчанию" этого
doStuff()
функция, которая вызывается для "каждого другого класса". В основном это будет работать, если сделать что-то вроде «если Param не получен из A и если не получен из B», но мне интересно, есть ли более элегантное решение.
#include <iostream>
class A {};
class B : public A {};
class X {};
class Y : public X {};
class Other {};
template <typename Param, std::enable_if_t<std::is_base_of<A, Param>::value, bool> = true>
void doStuff() {std::cout << "Base A" << std::endl;};
template <typename Param, std::enable_if_t<std::is_base_of<X, Param>::value, bool> = true>
void doStuff() {std::cout << "Base X" << std::endl;};
int main()
{
doStuff<B>();
doStuff<Y>();
// doStuff<Other>(); this is neither derived from B and Y, so call the default case
}
Решение должно работать с C++14.
2 ответа
Когда используешь
std:::enable_if
, вам нужно будет предоставить третью перегрузку SFINAE, которая обрабатывает условия по умолчанию, которые не обрабатываются другими перегрузками, например:
#include <iostream>
#include <type_traits>
class A {};
class B : public A {};
class X {};
class Y : public X {};
class Other {};
template <typename Param, std::enable_if_t<std::is_base_of<A, Param>::value, bool> = true>
void doStuff() { std::cout << "Base A" << std::endl; }
template <typename Param, std::enable_if_t<std::is_base_of<X, Param>::value, bool> = true>
void doStuff() { std::cout << "Base X" << std::endl; }
template <typename Param, std::enable_if_t<!(std::is_base_of<A, Param>::value || std::is_base_of<X, Param>::value), bool> = true>
void doStuff() { std::cout << "Something else" << std::endl; }
int main()
{
doStuff<B>(); // prints "Base A"
doStuff<Y>(); // prints "Base X"
doStuff<Other>(); // prints "Something else"
}
При этом в С++17 и более поздних версиях вы можете использовать
if constexpr
вместо этого, что чище, чем использование SFINAE в этой ситуации, например:
#include <iostream>
#include <type_traits>
class A {};
class B : public A {};
class X {};
class Y : public X {};
class Other {};
template <typename Param>
void doStuff() {
if constexpr (std::is_base_of_v<A, Param>)
std::cout << "Base A" << std::endl;
else if constexpr (std::is_base_of_v<X, Param>)
std::cout << "Base X" << std::endl;
else
std::cout << "Something else" << std::endl;
}
int main()
{
doStuff<B>(); // prints "Base A"
doStuff<Y>(); // prints "Base X"
doStuff<Other>(); // prints "Something else"
}
Или, если вы можете использовать C++17 или более позднюю версию, используйте constexpr, гораздо более читабельный, чем SFINAE.
#include <type_traits>
#include <iostream>
class A {};
class B : public A {};
class X {};
class Y : public X {};
class Other {};
template<typename type_t>
void doStuff()
{
if constexpr (std::is_base_of_v<A,type_t>)
{
std::cout << "Base A\n";
}
else
if constexpr (std::is_base_of_v<X, type_t>)
{
std::cout << "Base X\n";
}
else
{
std::cout << "Other\n";
}
};
int main()
{
doStuff<B>();
doStuff<Y>();
doStuff<Other>(); //this is neither derived from B and Y, so call the default case
return 0;
}