#c #templates #constructor #overload-resolution
#c #шаблоны #constructor #перегрузка-разрешение
Вопрос:
Возможно ли определить конструктор для всех производных типов и конструктор шаблона? Я написал этот тестовый пример, чтобы проиллюстрировать свою проблему:
#include <iostream>
class Variant;
class CustomVariant;
class Variant
{
public:
Variant(void)
{}
Variant(const Variantamp;)
{
std::cout << "ctor" << std::endl;
}
Variant(const CustomVariantamp;)
{
std::cout << "custom" << std::endl;
}
template<typename T>
Variant(const Tamp;)
{
std::cout << "template" << std::endl;
}
};
class CustomVariant : public Variant
{
};
class DerivedVariantA : public CustomVariant
{
};
class DerivedVariantB : public CustomVariant
{
};
int main(void)
{
DerivedVariantB dvb;
Variant v(dvb);
// expcected output: "custom" instead of "template"
}
Ответ №1:
template <typename T> Variant(const Tamp;) // (a)
Variant(const CustomVariantamp;) // (b)
Для вызова (a) преобразования не требуется; тип аргумента, DerivedVariantB
, точно соответствует where T = DerivedVariantB
.
Для вызова (b) требуется преобразование производного в базовое. Следовательно, (a) соответствует лучше, чем (b).
Если вы вызываете конструктор с аргументом типа CustomVariant
, оба конструктора являются точными совпадениями, поэтому выбирается (b), потому что при прочих равных условиях нетемплевидный вариант предпочтительнее шаблона.
Вы можете запретить использование шаблона, из которого T
получен Variant
, с помощью std::enable_if
:
template<typename T>
Variant(const Tamp;,
typename std::enable_if<
!std::is_base_of<Variant, T>::value, void*
>::type = 0)
{
std::cout << "template" << std::endl;
}
Это делает шаблон недоступным для создания, когда он T
является производным от Variant
, поэтому он не будет доступен во время разрешения перегрузки. enable_if
и is_base_of
являются новыми для C в C 0x, и ваш компилятор и стандартная библиотека могут их поддерживать. Если нет, вы также можете найти их в C TR1 или Boost.Признаки типов.
Комментарии:
1. Да, это правила, определенные стандартом. Вопрос в том, есть ли какой-либо обходной путь? 🙂
2. Вау, здорово. Это работает из-за SFINAE (значение не определено для типов, производных от Variant)?
3. @cytrinox: Да, именно так говорит SFINAE, и для этого
enable_if
и существует.
Ответ №2:
Нет, среди списка конструкторов, доступных в классе, нет конструктора, который принимает экземпляр type DerivedVariantB
в качестве аргумента. Следовательно, вызывается сгенерированный шаблон.
class DerivedVariantB ; // Forward Declaration
class Variant
{
public:
// ...
Variant( const DerivedVariantB amp;obj )
{
std::cout << "n DerivedVariantB n";
}
};
Теперь можно вызвать конструктор, который принимает ссылку типа DerivedVariantB
вместо сгенерированной шаблоном.