Шаблоны аргументов не соответствуют всем требованиям

Это возможно:

struct A {
    //void f();  < not declared in struct A
};

template<typename T>
struct Wrapper {
    T t;
    void call_f() { t.f(); }
};

int main() {
    Wrapper<A> w;
}

Это компилируется нормально, пока w.call_f() не вызывается. w.call_f() не может быть создан, потому что A::f не существует.

У меня возникла ситуация с таким шаблоном оболочки, который используется с разными типами T, которые не всегда реализуют все части интерфейса. (В основном, чтобы избежать дублирования кода).

Это не работает:

struct A {
    //using i_type = int;   < not declared/defined
};

template<typename T>
struct Wrapper {
    using its_i_type = typename T::i_type;
       // compile error, even if `i_type` gets never used
};

int main() {
    Wrapper<A> w;
}

и это не делает:

struct A {
    //using i_type = int;   < not declared/defined
};

template<typename T>
struct Wrapper {
    typename T::i_type call_f() { return 0; }
       // does not compile, even if `call_f()` is never instantiated
};

int main() {
    Wrapper<A> w;
}

Есть ли хороший способ справиться с этими ситуациями без большого количества дублирования кода (например, специализация для Wrapper и т. Д.)?


person tmlen    schedule 05.08.2016    source источник


Ответы (2)


Вы можете отложить вычет типа its_i_type. По сути, вы создаете простую оболочку, которую нужно пройти.


Чтобы распространить его на другие типы, которые вам нужны (я хотел предложить type_traits-подобное решение, но поскольку вам не нужны специализации), вы можете определить все типы, которые вам нужны:

template<typename T>
struct Wrapper {
private:
    template<typename U> struct i_typper { using type = typename U::i_type; };
    template<typename U> struct k_typper { using type = typename U::k_type; };
    template<typename U> struct p_typper { using type = typename U::p_type; };
public:
    using i_trait = i_typper<T>;
    using k_trait = k_typper<T>;
    using p_trait = p_typper<T>;
};

Пример:

struct A { using i_type = int; };
struct B { using i_type = int;    using k_type = float; };

int main() {
    Wrapper<A> w;   //Works now.

    Wrapper<A>::i_trait::type  mk1;  //Works
    Wrapper<A>::k_trait::type  mk2;  //Fails, not defined
    Wrapper<B>::i_trait::type  mk3;  //Works
    Wrapper<B>::k_trait::type  mk4;  //Works
}

В случае:

template<typename T>
struct Wrapper {
    typename T::i_type call_f() { return 0; }
       // does not compile, even if `call_f()` is never instantiated
};

Здесь у вас есть несколько вариантов:

  1. сделать эту функцию шаблоном функции-члена
  2. Используйте какую-то форму type_traits механизма, который все равно будет включать специализацию
  3. Пойдите путем абстрагирования общих Wrapper вещей в базовом классе WrapperBase;

Для первого варианта вам придется немного изменить его, чтобы дополнительно отложить вычет

template<typename T>
struct Wrapper {
private:
    template<typename U, typename> struct i_typper { using type = typename U::i_type; };
    template<typename U, typename> struct k_typper { using type = typename U::k_type; };
    template<typename U, typename> struct p_typper { using type = typename U::p_type; };
public:
    using i_trait = i_typper<T, void>;
    using k_trait = k_typper<T, void>;
    using p_trait = p_typper<T, void>;

    template<typename U = void>
    typename k_typper<T, U>::type call_f() { return 0; }
};

Я оставлю второй вариант в качестве упражнения: (в итоге может получиться что-то вроде:

template<typename T>
struct wrapper_traits {
    ....
};

template<>
struct wrapper_traits<A>{
   using ....
};

template<typename T>
struct Wrapper {
   ....
public:
    using i_trait = wrapper_traits<T>;
    using k_trait = wrapper_traits<T>;
    using p_trait = wrapper_traits<T>;
};

Ответ Джарода проще. Но это сработает, если у вас нет доступа к std::experimental или политика вашей компании запрещает вам ...

person WhiZTiM    schedule 05.08.2016
comment
А как насчет call_f, который возвращает i_type? - person aschepler; 05.08.2016
comment
@aschepler, тогда он потерпит неудачу. В этом случае я либо сделаю call_f функцией-членом шаблона, либо воспользуюсь type_trait-подобным решением, которое будет включать специализации - person WhiZTiM; 05.08.2016
comment
У вас есть два варианта [..] 1. [..] 2. [..] 3. [..] :-) (я знаю, многократное редактирование). - person Jarod42; 06.08.2016

С std::experimental::is_detected вы можете сделать

template<typename T>
using i_type_t = typename T::i_type;

template<typename T>
struct Wrapper {
    using its_i_type = typename std::experimental::detected_t<i_type_t, T>;
       // would be T::i_type or std::experimental::nonesuch
};

Или, чтобы лучше справиться с делом, что-то вроде:

template<typename T, bool = std::experimental::is_detected<i_type_t, T>::value>
struct WrapperWithIType {
    // Empty for false case.
};

template<typename T>
struct WrapperWithIType<T, true> {
    using its_i_type = i_type_t<T>;

    its_i_type call_f() { return 0; }
};

а потом

template<typename T>
struct Wrapper : WrapperWithIType<T> {
    // Common stuff
};
person Jarod42    schedule 05.08.2016
comment
@WhiZTiM он находится в TS (техническая спецификация, в настоящее время работает в g ​​++ - 6). - person TemplateRex; 07.08.2016