Изменить приоритет конструктора

#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 вместо сгенерированной шаблоном.