как заставить вывод аргумента работать для производного класса, который использует конструктор базового класса? - PullRequest
5 голосов
/ 19 апреля 2020

Когда производный класс использует конструктор базового класса, вычет кажется всегда fail . Однако, когда базовый класс имеет много конструкторов, очень неуклюже переопределять все конструкторы. Также неприятно, когда базовый класс быстро развивается с помощью новых конструкторов. Старый вопрос задавался более 2 лет go, поэтому мне интересно: есть ли способ обойти это в 2020 году, когда доступны c ++ 17 и c ++ 2a?

template<typename ...As>
class base_t
{
public:
    base_t(As... args){}
};

template<typename ...As>
class A_t: public base_t<As...>
{
public:
    A_t(As... args): base_t<As...>{args...} {};
};

template<typename ...As>
class B_t: public base_t<As...>
{
    using base_t<As...>::base_t;
};

int main()
{
    base_t a{1, 2.0f};
    A_t{1, 2.0f};
    B_t{1, 2.0f}; //fails unless explicitly specialize the template
    return 0;
}

обновления согласно @Sam и @Barry:

Руководство по выводам очень полезно. Однако, для немного более сложной ситуации, она все еще выходит из-под контроля:

template <typename A>
struct D_t {
    A x;
    D_t(A x) :x{x} {}
};
template<typename A, typename B>
class base2_t
{
public:
    base2_t(A a, B b){std::cout << "1\n";}
    base2_t(A a, D_t<B> c, int x){std::cout << "2\n";}
    base2_t(A a, B b, int x){std::cout << "3\n";}
    base2_t(A a, B b, int x, float y){std::cout << "4\n";}
    explicit base2_t(A(*fp)(B)){std::cout << "5\n";}
    // if we have lots of similar things like above
    // we will quickly end up write lots of different
    // guides.
};
template<typename A, typename B>
class C_t: public base2_t<A, B>
{
    using base2_t<A, B>::base2_t;
};
template<typename A, typename B, typename ...As>
C_t(A, B, As...)->C_t<A, B>;
template<typename A, typename B>
C_t(A(*)(B))->C_t<A, B>;
float func1(int x)
{
    return x;
}
int main()
{
    C_t{1, 2.0f, 3};
    base2_t{1, D_t{2.0f}, 3};
    C_t{1, D_t{2.0f}, 3}; // this is wrong, we have to deal with it by checking types and write different guides.
    base2_t{&func1};
    C_t{&func1};
}

Ответы [ 4 ]

3 голосов
/ 19 апреля 2020

Возможность наследовать руководства по выводам из базовых классов была предложенной для c ++ 20. Однако эта функция не была реализована, как говорится в последней строке:

Формулировка для CTAD от унаследованных конструкторов не была завершена вовремя для проекта комитета C ++ 20 и будет опубликовано в отдельной редакции позднее.

Итак, на данный момент вам нужно будет явно предоставить инструкции по выводу для производного класса (или определить конструктор, как вы это делали для * 1009). *). Надеюсь, это будет исправлено в c ++ 23.

2 голосов
/ 20 апреля 2020

К сожалению, в C ++ 20 нет универсального c способа наследования руководств по выводам базового класса. Было предложение об этом ( P1021 ), но оно не было сделано для C ++ 20.

Без этого предложения заманчиво предложить что-то вроде:

template <typename... Args>
Derived(Args&&... args) -> mp_rename<decltype(Base((Args&&)args...)), Derived>;

То есть выяснить тип, который вы получите, передав все args в Base, используя там вывод аргумента шаблона класса - а затем просто переименуйте все, что Base<Ts...> вы получите в Derived<Ts...>. Но грамматика для руководства по удержанию такова:

явный спецификатор opt имя-шаблона ( пункт-объявления-параметра ) -> идентификатор-простого шаблона ;

А в [temp.deduct.guide] / 3 добавлено ограничение:

имя-шаблона должно иметь тот же идентификатор, что и template-name из simple-template-id .

Таким образом, руководство по выводу для Derived должно заканчиваться точно -> Derived<Args...>. Там нет никакого способа засунуть туда какой-нибудь ум.

Без прямой языковой помощи и без возможности выполнять какие-либо шаблоны метапрограммирования здесь, вам остается только тщательно написать четкие руководства по выводам самостоятельно. К счастью, по крайней мере, здесь вы можете написать static_assert s, чтобы убедиться, что вы выполнили свою работу правильно:

template <typename... Ts>
inline constexpr bool matches = std::is_same_v<
    decltype(Base(std::declval<Ts>()...)),
    mp_rename<decltype(Derived(std::declval<Ts>()...)), Base>>;

static_assert(matches<int>); // or whatever is reasonable for your specific types
2 голосов
/ 19 апреля 2020

Одним из вариантов является явное руководство по выводу (C ++ 17):

template<typename ...As>
class base_t
{
public:
    base_t(As... args){}
};

template<typename ...As>
class B_t: public base_t<As...>
{
    using base_t<As...>::base_t;
};

template<typename ...As>
B_t( As...) -> B_t<As...>;

int main()
{
    base_t a{1, 2.0f};
    B_t b{1, 2.0f};

    B_t<int, float> bb=b;

    return 0;
}

Возможно, потребуется больше работы, чтобы правильно обработать ссылки, возможно, добавив std::remove_reference_cv (remove_cvref_t) для C ++ 20) или std::decay_t где-то там ...

1 голос
/ 21 апреля 2020

Вы можете использовать переименованную Идею @ Барри со слоем косвенности:

template<class... Args>
struct ctad_tag{};

template<class... Args>
struct deriv : base<Args...> {
    using base<Args...>::base;
};

template<class... Args>
struct deriv<ctad_tag<Args...>> : deriv<Args...> {
    using deriv<Args...>::deriv;
};

template<class... Args>
deriv(Args&&... args) 
-> deriv<rename_t<decltype(base(std::forward<Args>(args)...)), ctad_tag>>;

Но это немного хакерски, так как вы получите deriv<ctad_tag<...>> вместо deriv<...>. Тем не менее, в зависимости от варианта использования это может быть хорошо, поэтому я оставлю это здесь.

Рабочий пример: https://godbolt.org/z/ygWpJU

...