Дефинирани от потребителя спрямо приоритети на ръководството за приспадане на шаблони

Да кажем, че имаме клас като този с дефинирано от потребителя ръководство за приспадане:

template<typename T, typename... Args>
struct Foo
{
    Foo(Args&&...) { std::cout << "just Args: " << __PRETTY_FUNCTION__ << std::endl; }
    Foo(Args&&..., T&&) { std::cout << "Args and T: " << __PRETTY_FUNCTION__ << std::endl; }
};

template<typename... Args>
Foo(Args&&...) -> Foo<Void, Args...>;

Сега нека се опитаме да създадем екземпляр от този клас: Foo foo { 10 };. Какви биха били изведените аргументи на шаблона и какъв конструктор ще бъде извикан?

След известно експериментиране се оказва, че зависи от компилатора. А именно, gcc 7 и clang 6 (от trunk) изглежда избират автоматичното ръководство, създавайки T с int и Args с празен пакет, следователно изходът е

Args and T: Foo<T, Args>::Foo(Args&& ..., T&&) [with T = int; Args = {}]

clang 5, от друга страна, избира ръководството, дефинирано от потребителя:

just Args: Foo<Void, int>::Foo(Args &&...) [T = Void, Args = <int>]

Кой избор е правилният и как може да се използва дефинираното от потребителя ръководство за приспадане в този случай?

Пълният пример е наличен в wandbox.


person 0xd34df00d    schedule 17.12.2017    source източник


Отговори (1)


Да тръгнем от първите принципи. Опитът да се направи извод от Foo{10} включва разрешаване на претоварване на този набор:

template <typename T, typename... Args>
Foo<T, Args...> __f(Args&&... ); // ctor #1

template <typename T, typename... Args>
Foo<T, Args...> __f(Args&&..., T&&); // ctor #2

template <typename... Args>
Foo<Void, Args...> __f(Args&&... ); // deduction guide

Във функцията, синтезирана от първия конструктор, T е неизведен контекст. Във функцията, синтезирана от втория конструктор, Args е неизведен контекст. Така че нито едно от двете не е жизнеспособно. Ръководството за приспадане е жизнеспособно, така че тривиално е най-добрият жизнеспособен кандидат, така че в крайна сметка получаваме Foo<Void, int>.

След като сме там, отново извършваме разрешаване на претоварване, за да изберем конструктор. Това е по-просто, първото е жизнеспособно, а второто не, така че трябва да се извика.

Всяко друго поведение е грешка на компилатора (регистрирана 83447).

person Barry    schedule 17.12.2017
comment
Защо Args е неизведен контекст във функцията, синтезирана от ctor #2? Изглежда, че е напълно разбираемо: празен пакет в този конкретен случай. - person 0xd34df00d; 17.12.2017
comment
Добре, дадох прост пример, който е близък до това, което показахте в доклада за грешки, и той се компилира добре с всички gcc, които опитах, и двата clang 5 и 6, така че предположих, че е правилен. Линкът, който си дал, доказва, че не е! Интересното е, че clang ‹4 отхвърля кода. Що се отнася до момента, за да накарам кода да работи със съществуващите компилатори, добавих фиктивен параметър на етикет към втория ctor, тъй като този ctor така или иначе е детайл на изпълнението и не трябва да се извиква от потребители на класа. Благодаря много! - person 0xd34df00d; 17.12.2017