enable_if и конструктор с VS2012

Я пытаюсь условно включить шаблон конструктора. С полностью совместимым с C++11 компилятором я знаю, как это сделать, используя дополнительный аргумент шаблона по умолчанию. Однако мне нужно поддерживать VS2012, который имеет std::enable_if, но не поддерживает аргументы шаблона функции по умолчанию.

С C++11 я бы написал следующее:

template<typename T>
struct Class
{
  template<typename O, 
           typename = typename std::enable_if<std::is_convertible<O*, T*>::value>::type>
  Class(O*) {}
};

Я попробовал следующее, но он выдает ошибку C4336 и различные последующие ошибки:

template<typename T>
struct Class
{
  template <typename O>
  Class(O*, typename std::enable_if<std::is_convertible<O*, T*>::value>::type *= nullptr)
  {
  }
};

Есть ли способ заставить это работать с VS2012?

Дополнение:

Использование класса будет следующим:

struct X { };
struct X2 : X { };
struct Y { };

struct Client
{
  Client(Class<X> x) {}
  Client(Class<Y> y) {}
};

void test() {
  X2* x2;
  Client client(x2); // error C2668: ambiguous call to overloaded function
                     // (without std::enable_if)
}

3 ответа

Решение

Вы так близки к решению!

template<typename T>
struct Class
{
    template <typename O>
    Class(O*, typename std::enable_if<std::is_convertible<O*, T*>::value>::type * = nullptr)
    {
    }
};

Вы заметили разницу? *= внутри списка параметров был проанализирован как оператор умножения / присваивания, а не как тип указателя, за которым следует аргумент по умолчанию. Отсюда и синтаксические ошибки.

Это связано с тем, что синтаксический анализатор C++ определен для потребления максимально возможного количества символов при формировании токена (так называемое правило максимального перебора). Добавление пробела делит его на два отдельных токена, как и предполагалось.

Начиная с C++11, вы также можете использовать делегирующие конструкторы для этого:

template<typename T>
class Class {
    template<typename O>
    Class(O *o, std::true_type) {}

    template<typename O>
    Class(O *o, std::false_type) {}

public:
    template<typename O>
    Class(O *o): Class(o, typename std::is_convertible<O*, T*>::type) {}
};

Основная идея заключается в диспетчеризации тегов, и, возможно, она отлично работает и с VS2012.
Смотрите здесь для более подробной информации.

Боюсь, вам придется использовать вспомогательную функцию конструирования (я не нашел способ обойти это). Но что-то вроде этого должно работать:

#include <type_traits>

template<typename T>
struct Class
{
  template<typename O>
  Class(O* o) { construct(o, std::integral_constant<bool, std::is_convertible<T*, O*>::value>()); }

  template<class O>
  void construct(O*, std::true_type ) { /* convertible */ }
  template<class O>
  void construct(O*, ... ) { /* not convertible */ }
};

struct X { };
struct Y : public X { };

void check() {
  X x;
  int i;
  Class<Y> cl(&x);
  Class<Y> cl1(&i);
}
Другие вопросы по тегам