Error class template argument deduction failed

#include template struct mypair : std::pair { using std::pair::pair; }; int main() { (void)std::pair(2, 3); // It works ...

The short story: there is no rule in the standard that says how this would work, nor any rule that says that it doesn’t work. So GCC and Clang conservatively reject rather than inventing a (non-standard) rule.

The long story: mypair‘s pair base class is a dependent type, so lookup of its constructors cannot succeed. For each specialization of mytype<T1, T2>, the corresponding constructors of pair<T1, T2> are constructors of mytype, but this is not a rule that can be meaningfully applied to a template prior to instantiation in general.

In principle, there could be a rule that says that you look at the constructors of the primary pair template in this situation (much as we do when looking up constructors of mypair itself for class template argument deduction), but no such rule actually exists in the standard currently. Such a rule quickly falls down, though, when the base class becomes more complex:

template<typename T> struct my_pair2 : std::pair<T, T> {
  using pair::pair;
};

What constructors should be notionally injected here? And in cases like this, I think it’s reasonably clear that this lookup cannot possibly work:

template<typename T> struct my_pair3 : arbitrary_metafunction<T>::type {
  using arbitrary_metafunction<T>::type::type;
};

It’s possible we’ll get a rule change to allow deduction through your my_pair and the my_pair2 above if/when we get class template argument deduction rules for alias templates:

template<typename T> using my_pair3 = std::pair<T, T>;
my_pair3 mp3 = {1, 2};

The complexities involved here are largely the same as in the inherited constructor case. Faisal Vali (one of the other designers of class template argument deduction) has a concrete plan for how to make such cases work, but the C++ committee hasn’t discussed this extension yet.

Class template argument deduction (CTAD) C++17

Starting in C++17, when instantiating an object from a class template, the compiler can deduce the template types from the types of the object’s initializer (this is called class template argument deduction or CTAD for short). For example:

#include <utility> // for std::pair

int main()
{
    std::pair<int, int> p1{ 1, 2 }; // explicitly specify class template std::pair<int, int> (C++11 onward)
    std::pair p2{ 1, 2 };           // CTAD used to deduce std::pair<int, int> from the initializers (C++17)

    return 0;
}

CTAD is only performed if no template argument list is present. Therefore, both of the following are errors:

#include <utility> // for std::pair

int main()
{
    std::pair<> p1 { 1, 2 };    // error: too few template arguments, both arguments not deduced
    std::pair<int> p2 { 3, 4 }; // error: too few template arguments, second argument not deduced

    return 0;
}

Author’s note

Many future lessons on this site make use of CTAD. If you’re compiling these examples using the C++14 standard, you’ll get an error about missing template arguments. You’ll need to explicitly add such arguments to the example to make it compile.

Template argument deduction guides C++17

In most cases, CTAD works right out of the box. However, in certain cases, the compiler may need a little extra help understanding how to deduce the template arguments properly.

You may be surprised to find that the following program (which is almost identical to the example that uses std::pair above) doesn’t compile in C++17:

// define our own Pair type
template <typename T, typename U>
struct Pair
{
    T first{};
    U second{};
};

int main()
{
    Pair<int, int> p1{ 1, 2 }; // ok: we're explicitly specifying the template arguments
    Pair p2{ 1, 2 };           // compile error in C++17

    return 0;
}

If you compile this in C++17, you’ll likely get some error about “class template argument deduction failed” or “cannot deduce template arguments” or “No viable constructor or deduction guide”. This is because in C++17, CTAD doesn’t know how to deduce the template arguments for aggregate class templates. To address this, we can provide the compiler with a deduction guide, which tells the compiler how to deduce the template arguments for a given class template.

Here’s the same program with a deduction guide:

template <typename T, typename U>
struct Pair
{
    T first{};
    U second{};
};

// Here's a deduction guide for our Pair
// Pair objects initialized with arguments of type T and U should deduce to Pair<T, U>
template <typename T, typename U>
Pair(T, U) -> Pair<T, U>;
    
int main()
{
    Pair<int, int> p1{ 1, 2 }; // explicitly specify class template Pair<int, int> (C++11 onward)
    Pair p2{ 1, 2 };     // CTAD used to deduce Pair<int, int> from the initializers (C++17)

    return 0;
}

This example should compile under C++17.

The deduction guide for our Pair class is pretty simple, but let’s take a closer look at how it works.

// Here's a deduction guide for our Pair
// Pair objects initialized with arguments of type T and U should deduce to Pair<T, U>
template <typename T, typename U>
Pair(T, U) -> Pair<T, U>;

First, we use the same template type definition as in our Pair class. This makes sense, because if our deduction guide is going to tell the compiler how to deduce the types for a Pair<T, U>, we have to define what T and U are (template types). Second, on the right hand side of the arrow, we have the type that we’re helping the compiler to deduce. In this case, we want the compiler to be able to deduce template arguments for objects of type Pair<T, U>, so that’s exactly what we put here. Finally, on the left side of the arrow, we tell the compiler what kind of declaration to look for. In this case, we’re telling it to look for a declaration of some object named Pair with two arguments (one of type T, the other of type U). We could also write this as Pair(T t, U u) (where t and u are the names of the parameters, but since we don’t use t and u, we don’t need to give them names).

Putting it all together, we’re telling the compiler that if it sees a declaration of a Pair with two arguments (of types T and U respectively), it should deduce the type to be a Pair<T, U>.

So when the compiler sees the definition Pair p2{ 1, 2 }; in our program, it will say, “oh, this is a declaration of a Pair and there are two arguments of type int and int, so using the deduction guide, I should deduce this to be a Pair<int, int>“.

Here’s a similar example for a Pair that takes a single template type:

template <typename T>
struct Pair
{
    T first{};
    T second{};
};

// Here's a deduction guide for our Pair
// pair objects initialized with arguments of type T and T should deduce to Pair<T>
template <typename T>
Pair(T, T) -> Pair<T>;

int main()
{
    Pair<int> p1{ 1, 2 }; // explicitly specify class template Pair<int> (C++11 onward)
    Pair p2{ 1, 2 };     // CTAD used to deduce Pair<int, int> from the initializers (C++17)

    return 0;
}

In this case, our deduction guide maps a Pair(T, T) (a Pair with two arguments of type T) to a Pair<T>.

Author’s note

A few notes about deduction guides.

First, std::pair (and other standard library template types) come with pre-defined deduction guides. This is why our example above that uses std::pair compiles fine in C++17 without us having to provide deduction guides ourselves.

Second, C++20 added the ability for the compiler to automatically generate deduction guides for aggregate class types, so the version of Pair without the deduction guides should compile in C++20. This assumes your compiler supports feature P1816, which as of the time of writing, gcc and Visual Studio do, and Clang does not.

Стандарт C++17 добавил в язык новую фичу: Class Template Argument Deduction (CTAD). Вместе с новыми возможностями в C++ традиционно добавились и новые способы отстрела собственных конечностей. В этой статье мы будем разбираться, что из себя представляет CTAD, для чего используется, как упрощает жизнь, и какие в нём есть подводные камни.

Начнём издалека

Вспомним, что такое вообще Template Argument Deduction, и для чего он нужен. Если вы достаточно уверенно чувствуете себя с шаблонами C++, этот раздел можно пропустить и сразу переходить к следующему.

До C++17 вывод параметров шаблона относился только к шаблонам функций. При инстанцировании шаблона функции можно явно не указывать те аргументы шаблона, которые могут быть выведены из типов фактических аргументов функции. Правила выведения довольно сложны, им посвящён целый раздел 17.9.2 в Стандарте [temp.deduct] (здесь и далее я ссылаюсь на свободно доступную версию драфта Стандарта; в будущих версиях нумерация разделов может измениться, поэтому я рекомендую искать по мнемоническому коду, указанному в квадратных скобках).

Мы не будем подробно разбирать все тонкости этих правил, они нужны разве что разработчикам компиляторов. Для практического применения достаточно запомнить простое правило: компилятор может самостоятельно вывести аргументы шаблона функции, если это можно сделать однозначно на основании имеющейся информации. При выведении типов параметров шаблона применяются стандартные преобразования как при вызове обычной функции (отбрасывается const у литеральных типов, массивы сводятся к указателям, ссылки на функции приводятся к указателям на функции и т.д.).

template <typename T>
void func(T t) {
    // ...
}

int some_func(double d) {
  return static_cast<int>(d);
}

int main() {
    const int i = 123;
    func(i);  // func<int>

    char arr[] = "Some text";
    func(arr);  // func<char *>

    func(some_func);  // func<int (*)(double)>

    return 0;
}

Всё это упрощает использование шаблонов функций, но, увы, совсем неприменимо к шаблонам классов. При инстанциировании шаблонов классов все недефолтные параметры шаблонов приходилось указывать явно. В связи с этим неприятным свойством в стандартной библиотеке появилось целое семейство свободных функций с префиксом make_: make_unique, make_shared, make_pair, make_tuple и т.д.

// Вместо
auto tup1 = std::tuple<int, char, double>(123, 'a', 40.0);
// можно использовать
auto tup2 = std::make_tuple(123, 'a', 40.0);

Новое в C++17

В новом Стандарте по аналогии с параметрами шаблонов функций параметры шаблонов классов выводятся из аргументов вызываемых конструкторов:

std::pair pr(false, 45.67);      // std::pair<bool, double>
std::tuple tup(123, 'a', 40.0);  // std::tuple<int, char, double>
std::less l;                     // std::less<void>, больше не надо писать std::less<> l

template <typename T> struct A { A(T,T); };
auto y = new A{1, 2};                // выводится A<int>

auto lck = std::lock_guard(mtx);     // std::lock_guard<std::mutex>
std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // не надо явно указывать тип итератора

template <typename T> struct F { F(T); }
std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // F<lambda>

Сразу стоит упомянуть об ограничениях CTAD, которые действуют на момент C++17 (возможно, эти ограничения уберут в будущих версиях Стандарта):

  • CTAD не работает с алиасами шаблонов:

template <typename X>
using PairIntX = std::pair<int, X>;

PairIntX p{1, true}; // не компилируется

  • CTAD не позволяет частично выводить аргументы (как это работает для обычного Template Argument Deduction):

std::pair p{1, 5};              // OK
std::pair<double> q{1, 5};      // ошибка, так нельзя
std::pair<double, int> r{1, 5}; // OK

Также компилятор не сможет вывести типы параметров шаблона, которые явно не связаны с типами аргументов конструктора. Простейший пример – конструктор контейнера, принимающий пару итераторов:

template <typename T>
struct MyVector {
  template <typename It>
  MyVector(It from, It to);
};

std::vector<double> dv = {1.0, 3.0, 5.0, 7.0};
MyVector v2{dv.begin(), dv.end()}; // не могу вывести тип T из типа It

Тип It не связан напрямую с T, хотя мы, разработчики, совершенно точно знаем, как его можно получить. Для того, чтобы подсказать компилятору, как выводить несвязанные напрямую типы, в C++17 появилась новая языковая конструкция – deduction guide, которую мы рассмотрим в следующем разделе.

Deduction guides

Для примера выше deduction guide будет выглядеть так:

template <typename It>
MyVector(It, It) -> MyVector<typename std::iterator_traits<It>::value_type>;

Здесь мы подсказываем компилятору, что для конструктора с двумя параметрами одинакового типа можно определить тип T с помощью конструкции std::iterator_traits<It>::value_type. Обратите внимание, что deduction guides находятся вне определения класса, это позволяет настраивать поведение внешних классов, в том числе и классов из Стандартной библиотеки C++.

Формальное описание синтаксиса deduction guides приводится в Стандарте C++17 в разделе 17.10 [temp.deduct.guide]:

[explicit] template-name (parameter-declaration-clause) -> simple-template-id;

Ключевое слово explicit перед deduction guide запрещает применять его при copy-list-initialization:

template <typename It>
explicit MyVector(It, It) -> MyVector<typename std::iterator_traits<It>::value_type>;

std::vector<double> dv = {1.0, 3.0, 5.0, 7.0};
MyVector v2{dv.begin(), dv.end()};    // ОК
MyVector v3 = {dv.begin(), dv.end()}; // ошибка компиляции

Кстати, deduction guide не обязательно должен быть шаблоном:

template<class T> struct S { S(T); };
S(char const*) -> S<std::string>;
S s{"hello"}; // S<std::string>

Подробный алгоритм работы CTAD

Формальные правила выведения аргументов шаблонов классов подробно описаны в пункте 16.3.1.8 [over.match.class.deduct] Стандарта C++17. Попробуем в них разобраться.

Итак, у нас есть шаблонный тип C, для которого применяется CTAD. Для того, чтобы выбрать, какой именно конструктор и с какими параметрами вызывать, для C формируется множество шаблонных функций по следующим правилам:

  • Для каждого конструктора Ci генерируется фиктивная шаблонная функция Fi. Шаблонные параметры Fi – это параметры C, за которыми следуют шаблонные параметры Ci (если они имеются), включая параметры со значениями по умолчанию. Типы параметров функции Fi соответствуют типам параметров конструктора Ci. Возвращает фиктивная функция Fi тип C с аргументами, соответствующими шаблонным параметрам C.

Псевдокод:

template <typename T, typename U>
class C {
 public:
  template <typename V, typename W = A>
  C(V, W);
};
// генерирует фиктивную функцию
template <typename T, typename U, typename V, typename W = A>
C<T, U> Fi(V, W);

  • Если тип C не определён, или конструкторов в нём не задано, вышеописанные правила применяются для гипотетического конструктора C().
  • Дополнительная фиктивная функция генерируется для конструктора , для неё даже придумали специальное название: copy deduction candidate.
  • Для каждого deduction guide также генерируется фиктивная функция Fi с шаблонными параметрами и аргументами deduction guide и возвращаемым значением, соответствующим типу справа от -> в deduction guide (в формальном определении он называется simple-template-id).

Псевдокод:

template <typename T, typename V>
C(T, V) -> C<typename DT<T>, typename DT<V>>;
// генерирует фиктивную функцию
template <typename T, typename V>
C<typename DT<T>, typename DT<V>> Fi(T,V);

Далее, для полученного набора фиктивных функций Fi применяются обычные правила вывода шаблонных параметров и разрешения перегрузок с единственным исключением: когда фиктивная функция вызвана со списком инициализации, состоящим из единственного параметра с типом cv U, где U – специализация C или тип, унаследованный от специализации C (на всякий случай уточню, что cv == const volatile; такая запись означает, типы U, const U, volatile U и const volatile U трактуются в одинаково), пропускается правило, дающее приоритет конструктору C(std::initializer_list<>) (за подробностями list initialization можно обратиться к пункту 16.3.1.7 [over.match.list] Стандарта C++17). Пример:

std::vector v1{1, 2};   // std::vector<int>
std::vector v2{v1};     // std::vector<int>, а не std::vector<std::vector<int>>

Наконец, если удалось выбрать единственную наиболее подходящую фиктивную функцию, то выбирается соответствующий ей конструктор или deduction guide. Если же подходящих нет, либо есть несколько одинаково хорошо подходящих, компилятор сообщает об ошибке.

Подводные камни

CTAD применяется при инициализации объектов, а инициализация традиционно очень запутанная часть языка C++. С добавлением в C++11 универсальной инициализации (uniform initialization) способов отстрелить себе ногу только прибавилось. Теперь вызвать конструктор для объекта можно как с круглыми, так и с фигурными скобками. Во многих случаях оба этих варианта работают одинаково, но далеко не всегда:

std::vector v1{8, 15}; // [8, 15]
std::vector v2(8, 15); // [15, 15, … 15] (8 раз)
std::vector v3{8};     // [8]
std::vector v4(8);     // не компилируется

Пока всё вроде бы достаточно логично: v1 и v3 вызывают конструктор, принимающий std::initializer_list<int>, int выводится из параметров; v4 не может найти конструктор, принимающий всего один параметр типа int. Но это ещё цветочки, ягодки впереди:

std::vector v5{"hi", "world"}; // [“hi”, “world”]
std::vector v6("hi", "world"); // ??

v5, как и ожидается, будет типа std::vector<const char*> и инициализируется двумя элементами, а вот следующая строка делает нечто совсем другое. Для вектора есть всего один конструктор, принимающий два параметра одного типа:

template< class InputIt >
vector( InputIt first, InputIt last,
        const Allocator& alloc = Allocator() );

благодаря deduction guide для std::vector «hi» и «world» будут трактоваться как итераторы, и в вектор типа std::vector<char> будут добавлены все элементы, лежащие «между» ними. Если нам повезёт и эти две строковые константы находятся в памяти подряд, то в вектор попадут три элемента: ‘h’, ‘i’, ‘x00’, но, скорее всего, такой код приведёт к нарушению защиты памяти и аварийному завершению программы.

Используемые материалы

Драфт Стандарта C++17
CTAD
CppCon 2018: Stephan T. Lavavej «Class Template Argument Deduction for Everyone»

Рассмотрим следующий код:

#include <utility>

template<typename T>
struct wrapper {
T value;
};

struct foo {
operator wrapper<int>() {
return{10};
}
};int main() {
foo f;
wrapper w = f; // error
std::pair p = std::make_pair(1, 0); // ok
}

gcc 7.1.1 не в состоянии скомпилировать в отмеченной строке выше:

main.cpp: In function 'int main()':
main.cpp:17:17: error: class template argument deduction failed:
wrapper w = f; // error
^
main.cpp:17:17: error: no matching function for call to 'wrapper(foo&)'
main.cpp:4:8: note: candidate: template<class T> wrapper(wrapper<T>)-> wrapper<T>
struct wrapper {
^~~~~~~
main.cpp:4:8: note:   template argument deduction/substitution failed:
main.cpp:17:17: note:   'foo' is not derived from 'wrapper<T>'
wrapper w = f; // error
^

f конвертируется в wrapper<int>так что я ожидаю, что это произойдет. Оттуда компилятор должен уметь выводить, что T является int, Но это не может.

Компилятор может вывести std::pairПараметр шаблона правильно, поэтому мне интересно, почему это не так с wrapper,

Есть идеи?

13

Решение

Для вывода аргумента шаблона класса «набор перегрузки» составляется так, как описано в [Over.match.class.deduct / 1]. Это следующие:

Набор функций и шаблонов функций состоит из:
(1.1) — Для каждого конструктора первичного класса назначается шаблон
по имени шаблона, если шаблон определен, шаблон функции
со следующими свойствами:
(1.1.1) — параметры шаблона
параметры шаблона шаблона класса, сопровождаемого шаблоном
параметры (включая аргументы шаблона по умолчанию) конструктора,
если есть
(1.1.2) — Типы параметров функции:
конструктор
(1.1.3) — тип возвращаемого значения — шаблон класса
специализация, обозначаемая аргументами имя-шаблона и шаблона
соответствующие параметрам шаблона, полученным из класса
шаблон.

(1.2) — Если первичный шаблон класса C не определен или не определен
объявить любые конструкторы, дополнительный шаблон функции, получаемый как
выше от гипотетического конструктора C ().

(1.3) — Дополнительный шаблон функции, полученный, как указано выше, из
гипотетический конструктор C (C), называемый кандидатом на вычет копии.

(1.4) — Для каждого руководства по выводу, функция или шаблон функции с
следующие свойства:
(1.4.1) — параметры шаблона, если таковые имеются,
и параметры функции — те из руководства по выводу.
(1.4.2) — Тип возвращаемого значения — это простой-идентификатор-шаблона руководства по выводу.

Как вы можете видеть, соответствующая «функция» в 1.1 только пытается сопоставить типы аргументов с типами параметров шаблона. именно так. Он не учитывает преобразование (так же, как и большинство других типов поведения, связанных с удержанием шаблонов).

Причина, по которой это работает std::pair это связано с пунктом 1.3 и «кандидатом на вычет при копировании», который он определяет.

11

Другие решения

Других решений пока нет …

Praktolock

73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

1

19.02.2021, 18:05. Показов 2437. Ответов 14

Метки нет (Все метки)


Здравствуйте!

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <class T>
struct A
{
  struct B
  {
    
  };
};
 
template <class T>
typename A<T>::B operator+(const typename A<T>::B& _l, const typename A<T>::B&_r)
{
  return typename A<T>::B();
}
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  c = a + b;
  return 0;
}

компилятор говорит:

no match for ‘operator+’ (operand types are ‘A<int>::B’ and ‘A<int>::B’)

template argument deduction/substitution failed:
couldn’t deduce template parameter ‘T’

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 18:33

2

Цитата
Сообщение от Praktolock
Посмотреть сообщение

компилятор говорит:

Правильно говорит. Так вывести тип параметров не получится — из класса B нельзя определить тип класса A



0



Вездепух

Эксперт CЭксперт С++

10435 / 5704 / 1553

Регистрация: 18.10.2014

Сообщений: 14,098

19.02.2021, 18:45

3



1



oleg-m1973

6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 18:59

4

Только вот так

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <class T>
struct A
{
    struct B
    {
        using type = T;
    };
};
 
template <class T>
auto operator+(const T &_l, const typename A<typename T::type>::B &_r) -> typename A<typename T::type>::B
{
  return typename A<typename T::type>::B();
}
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  c = a + b;
  return 0;
}



0



Praktolock

73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 19:07

 [ТС]

5

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Только вот так

Что-то много букв. Тогда уж так (как я и решил сделать):

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <class T>
struct A
{
  struct B
  {
    typedef T TYPE;
  };
};
 
template <class T>
T operator+(const T&_l, const T&_r)
{
  return T();
}
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  
  a = b + c;
  return 0;
}



0



oleg-m1973

6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 19:11

6

Цитата
Сообщение от Praktolock
Посмотреть сообщение

Что-то много букв. Тогда уж так (как я и решил сделать):

Да уж. Этот плюс у тебя будет вызываться вообще для всех типов, для которых нет специального оператора

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <class T>
struct A
{
  struct B
  {
    typedef T TYPE;
  };
};
 
template <class T>
T operator+(const T&_l, const T&_r)
{
std::cout << __FUNCSIG__ << std::endl;
  return T();
}
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  
  a = b + c;
 
 
  std::unique_ptr<int> s1, s2, s3;
  s1 = s2 + s3;
  return 0;
}

Но раз решил, то делай



0



73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 19:13

 [ТС]

7

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Этот плюс у тебя будет вызываться вообще для всех типов

Есть такое) Но меня это, как ни странно, устраивает



0



oleg-m1973

6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 19:18

8

Цитата
Сообщение от Praktolock
Посмотреть сообщение

Есть такое) Но меня это, как ни странно, устраивает

Сделай по-человечески

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <class T>
struct A
{
    struct B
    {
        B operator +(const B &other) const
        {
            return B();
        }
    };
};
 
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  a = b + c;
 
  return 0;
}



0



73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 19:29

 [ТС]

9

В теле оператора, все таки будут вызываться методы этого самого типа T, и конпилятор не сможет сгенерировать тело оператора, для типов, для которых я не планировал вызов этого оператора.

Добавлено через 3 минуты

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Сделай по-человечески

к сожалению, этот очевидный вариант, меня не устраивает

Добавлено через 4 минуты
Впрочем, как и тот, который я решил использовать. Жизнь боль…



0



TheCalligrapher

Вездепух

Эксперт CЭксперт С++

10435 / 5704 / 1553

Регистрация: 18.10.2014

Сообщений: 14,098

19.02.2021, 19:40

10

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Только вот так

Не только. В данном случае можно было обойтись без шаблонности оператора сложения, т.е. можно было (и нужно было!) вместо этого воспользоваться лазейкой с friend

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class T>
struct A
{
  struct B
  {
    friend B operator +(const B &_l, const B &_r)
    {
      return B();
    }
  };
};
 
int main(int argc, char**argv)
{
  A<int>::B a, b, c;
  c = a + b;
}



1



Praktolock

73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 19:41

 [ТС]

11

Попробую, добавить немного конкретики.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
template <class T>
struct A
{
  struct B
  {
    
  };
  
  struct C
  {
    C() {}
    C(const B&) {}
    operator B() { return B(); }
    C& operator =(const B&) { return *this; }
  };
  
  struct D
  {
    D() {}
    D(const B&) {}
    operator B() { return B(); }
    D& operator =(const B&) { return *this; }
  };
};
 
typedef A<int> AA;
typedef AA::B B;
typedef AA::C C;
typedef AA::D D;
 
template <class T>
A<T>::B operator+(const A<T>::B&_l, const A<T>::B&_r)
{
  A<T>::B result(_l);
  //...
  return result;
}
 
int main(int argc, char**argv)
{
  B b;
  C c;
  D d;
  
  d = b + c;
  
  return 0;
}

Кроме класса B, есть еще C и D. Все математические операции над ними должны проводиться через приведение их к типу B. Если делать оператор + как функцию член, в каждом из них нужно будет писать примерно одно и то-же. Хотелось бы один оператор объявить…



0



6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 20:10

12

Цитата
Сообщение от Praktolock
Посмотреть сообщение

Кроме класса B, есть еще C и D. Все математические операции над ними должны проводиться через приведение их к типу B. Если делать оператор + как функцию член, в каждом из них нужно будет писать примерно одно и то-же. Хотелось бы один оператор объявить…

Если они все будут приводиться к типу B, то достаточно этот оператор объявить в классе B



0



Praktolock

73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 20:13

 [ТС]

13

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Если они все будут приводиться к типу B, то достаточно этот оператор объявить в классе B

C++
1
2
3
4
5
6
7
8
9
10
11
typedef A<int> AA;
typedef AA::B B;
typedef AA::C C;
typedef AA::D D;
void main()
{
  B b;
  C c;
  D d;
  b = c + d;
}

скомпилируется?



0



oleg-m1973

6574 / 4559 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

19.02.2021, 20:15

14

Лучший ответ Сообщение было отмечено Praktolock как решение

Решение

На худой конец вот так

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template <class T>
struct A
{
  struct B
  {
    
  };
  
  struct C
  {
    C() {}
    C(const B&) {}
    operator B() { return B(); }
    C& operator =(const B&) { return *this; }
  };
  
  struct D
  {
    D() {}
    D(const B&) {}
    operator B() { return B(); }
    D& operator =(const B&) { return *this; }
  };
 
   friend B operator +(const B &a, const B &b)
  {
     .................
  }
 
};

Добавлено через 1 минуту

Цитата
Сообщение от Praktolock
Посмотреть сообщение

скомпилируется?

Конечно нет. Думаешь любой код нафигачишь и всё будет компилироваться?



1



73 / 73 / 18

Регистрация: 29.11.2011

Сообщений: 356

19.02.2021, 20:19

 [ТС]

15

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

вот так

то что нужно

Добавлено через 33 секунды

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Конечно нет. Думаешь любой код нафигачишь и всё будет компилироваться?

Риторический вопрос был



0



Type: LanguageService

  • OS and Version: Arch Linux with Gnome 3
  • VS Code Version: 1.29.1
  • C/C++ Extension Version: 0.20.1

I am having an issue where vscode is telling me I have an compile error when it compiles correctly with gcc and clang. Here is an c++ example that triggers the issue:

struct A {};

template<typename T>
struct B : public A {
	B(int p1, T p2) {  }
};

int main()
{
	A* test1 = new B{ 5, 5.0 }; // < Error

	double dtest = 5.0;
	A* test2 = new B{ 5, dtest }; // < Error

	B<double>* test3 = new B{ 5, 5.0 }; // < Error

	A* test4 = new B<double>{ 5, 5.0 }; // < Works Correctly
}

Intellisense is saying there is an error: too many initializer values. However the code compiles correctly with gcc 8.2 and clang 7.

screenshot from 2018-12-08 22-14-01

My c_cpp_properties.json is as follows:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

Does not resolve issue when using clang-x64.

In order to instantiate a function template, every template argument must be known, but not every template argument has to be specified. When possible, the compiler will deduce the missing template arguments from the function arguments. This occurs when a function call is attempted, when an address of a function template is taken, and in some other contexts:

template<typename To, typename From>
To convert(From f);
 
void g(double d) 
{
    int i = convert<int>(d);    // calls convert<int, double>(double)
    char c = convert<char>(d);  // calls convert<char, double>(double)
    int(*ptr)(float) = convert; // instantiates convert<int, float>(float) 
                                // and stores its address in ptr
}

This mechanism makes it possible to use template operators, since there is no syntax to specify template arguments for an operator other than by re-writing it as a function call expression:

#include <iostream>
 
int main() 
{
    std::cout << "Hello, world" << std::endl;
    // operator<< is looked up via ADL as std::operator<<,
    // then deduced to operator<<<char, std::char_traits<char>> both times
    // std::endl is deduced to &std::endl<char, std::char_traits<char>>
}

Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.

Contents

  • 1 Deduction from a function call
    • 1.1 Non-deduced contexts
    • 1.2 Deduction from a type
  • 2 Other contexts
    • 2.1 auto type deduction
    • 2.2 auto-returning functions
    • 2.3 Overload resolution
    • 2.4 Address of an overload set
    • 2.5 Partial ordering
    • 2.6 Conversion function template
    • 2.7 Explicit instantiation
    • 2.8 Deallocation function template
  • 3 Alias templates
  • 4 Implicit conversions
  • 5 Defect reports

[edit] Deduction from a function call

Template argument deduction attempts to determine template arguments (types for type template parameters Ti, templates for template template parameters TTi, and values for non-type template parameters Ii), which can be substituted into each parameter P to produce the type deduced A, which is the same as the type of the argument A, after adjustments listed below.

If there are multiple parameters, each P/A pair is deduced separately and the deduced template arguments are then combined. If deduction fails or is ambiguous for any P/A pair or if different pairs yield different deduced template arguments, or if any template argument remains neither deduced nor explicitly specified, compilation fails.

If removing references and cv-qualifiers from P gives std::initializer_list<P‘> and A is a braced-init-list, then deduction is performed for every element of the initializer list, taking P' as the parameter and the list element A' as the argument:

template<class T>
void f(std::initializer_list<T>);
 
f({1, 2, 3});  // P = std::initializer_list<T>, A = {1, 2, 3}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = 2: deduced T = int
               // P'3 = T, A'3 = 3: deduced T = int
               // OK: deduced T = int
 
f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = "abc": deduced T = const char*
               // error: deduction fails, T is ambiguous

If removing references and cv-qualifiers from P gives P'[N], and A is a non-empty braced-init-list, then deduction is performed as above, except if N is a non-type template parameter, it is deduced from the length of the initializer list:

template<class T, int N>
void h(T const(&)[N]);
h({1, 2, 3}); // deduced T = int, deduced N = 3
 
template<class T>
void j(T const(&)[3]);
j({42}); // deduced T = int, array bound is not a parameter, not considered
 
struct Aggr { int i; int j; };
 
template<int N>
void k(Aggr const(&)[N]);
k({1, 2, 3});       // error: deduction fails, no conversion from int to Aggr
k({{1}, {2}, {3}}); // OK: deduced N = 3
 
template<int M, int N>
void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2
 
template<class T, int N>
void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3

If a parameter pack appears as the last P, then the type P is matched against the type A of each remaining argument of the call. Each match deduces the template arguments for the next position in the pack expansion:

template<class... Types>
void f(Types&...);
 
void h(int x, float& y)
{
    const int z = x;
    f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int
                // P = Types&..., A2 = y: deduced second member of Types... = float
                // P = Types&..., A3 = z: deduced third member of Types... = const int
                // calls f<int, float, const int>
}
(since C++11)

If P is a function type, pointer to function type, or pointer to member function type and if A is a set of overloaded functions not containing function templates, template argument deduction is attempted with each overload. If only one succeeds, that successful deduction is used. If none or more than one succeeds, the template parameter is non-deduced context (see below):

template<class T>
int f(T(*p)(T));
 
int g(int);
int g(char);
 
f(g); // P = T(*)(T), A = overload set
      // P = T(*)(T), A1 = int(int): deduced T = int
      // P = T(*)(T), A2 = int(char): fails to deduce T
      // only one overload works, deduction succeeds

Before deduction begins, the following adjustments to P and A are made:

1) If P is not a reference type,

a) if A is an array type, A is replaced by the pointer type obtained from array-to-pointer conversion;

b) otherwise, if A is a function type, A is replaced by the pointer type obtained from function-to-pointer conversion;

c) otherwise, if A is a cv-qualified type, the top-level cv-qualifiers are ignored for deduction:

template<class T>
void f(T);
 
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
 
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
 
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int

2) If P is a cv-qualified type, the top-level cv-qualifiers are ignored for deduction.

3) If P is a reference type, the referenced type is used for deduction.

4) If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction (Note: this is the basis for the action of std::forward Note: in class template argument deduction, template parameter of a class template is never a forwarding reference (since C++17)):

template<class T>
int f(T&&);       // P is an rvalue reference to cv-unqualified T (forwarding reference)
 
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
 
int main()
{
    int i;
    int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
    int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
 
//  int n3 = g(i); // error: deduces to g<int>(const int&&), which
                   // cannot bind an rvalue reference to an lvalue
}

After these transformations, the deduction processes as described below (cf. section Deduction from a type) and attempts to find such template arguments that would make the deduced A (that is, P after adjustments listed above and the substitution of the deduced template parameters) identical to the transformed A, that is A after the adjustments listed above.

If the usual deduction from P and A fails, the following alternatives are additionally considered:

1) If P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A:

template<typename T>
void f(const T& t);
 
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
      // deduced T = bool, deduced A = const bool
      // deduced A is more cv-qualified than A

2) The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversions or a function pointer conversion (since C++17):

template<typename T>
void f(const T*);
 
int* p;
f(p); // P = const T*, A = int*:
      // deduced T = int, deduced A = const int*
      // qualification conversion applies (from int* to const int*)

3) If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A:

template<class T>
struct B {};
 
template<class T>
struct D : public B<T> {};
 
template<class T>
void f(B<T>&) {}
 
void f()
{
    D<int> d;
    f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>:
          // deduced T = int, deduced A = B<int>
          // A is derived from deduced A
}

[edit] Non-deduced contexts

In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:

// the identity template, often used to exclude specific arguments from deduction
// (available as std::type_identity as of C++20)
template<typename T>
struct identity { typedef T type; };
 
template<typename T>
void bad(std::vector<T> x, T value = 1);
 
template<typename T>
void good(std::vector<T> x, typename identity<T>::type value = 1);
 
std::vector<std::complex<double>> x;
 
bad(x, 1.2);  // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = T, A2 = double
              // P2/A2: deduced T = double
              // error: deduction fails, T is ambiguous
 
good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = identity<T>::type, A2 = double
              // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
              // OK: T = std::complex<double>

2) The expression of a decltype-specifier:

template<typename T>
void f(decltype(*std::declval<T>()) arg);
 
int n;
f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context
(since C++11)

3) A non-type template argument or an array bound in which a subexpression references a template parameter:

template<std::size_t N>
void f(std::array<int, 2 * N> a);
 
std::array<int, 10> a;
f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>:
      // 2 * N is non-deduced context, N cannot be deduced
      // note: f(std::array<int, N> a) would be able to deduce N

4) A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done:

template<typename T, typename F>
void f(const std::vector<T>& v, const F& comp = std::less<T>());
 
std::vector<std::string> v(3);
f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue
      // P1/A1 deduced T = std::string
      // P2 = const F&, A2 = std::less<std::string> rvalue
      // P2 is non-deduced context for F (template parameter) used in the
      // parameter type (const F&) of the function parameter comp,
      // that has a default argument that is being used in the call f(v)

5) The parameter P, whose A is a function or a set of overloads such that more than one function matches P or no function matches P or the set of overloads includes one or more function templates:

template<typename T>
void out(const T& value) { std::cout << value; }
 
out("123");     // P = const T&, A = const char[4] lvalue: deduced T = char[4]
out(std::endl); // P = const T&, A = function template: T is in non-deduced context

6) The parameter P, whose A is a braced-init-list, but P is not std::initializer_list, a reference to one (possibly cv-qualified), or a reference to an array (since C++17):

template<class T>
void g1(std::vector<T>);
 
template<class T>
void g2(std::vector<T>, T x);
 
g1({1, 2, 3});     // P = std::vector<T>, A = {1, 2, 3}: T is in non-deduced context
                   // error: T is not explicitly specified or deduced from another P/A
 
g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T is in non-deduced context
                   // P2 = T, A2 = int: deduced T = int

7) The parameter P which is a parameter pack and does not occur at the end of the parameter list:

template<class... Ts, class T>
void f1(T n, Ts... args);
 
template<class... Ts, class T>
void f2(Ts... args, T n);
 
f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int
                // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int]
 
f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context

8) The template parameter list that appears within the parameter P, and which includes a pack expansion that is not at the very end of the template parameter list:

template<int...>
struct T {};
 
template<int... Ts1, int N, int... Ts2>
void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&);
 
template<int... Ts1, int N, int... Ts2>
void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&);
 
T<1, 2> t1;
T<1, -1, 0> t2;
 
good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>:
              // deduced N = 1, deduced Ts1 = [2]
              // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>:
              // deduced N = 1, deduced Ts2 = [-1, 0]
 
bad(t1, t2);  // P1 = const T<Ts1..., N>&, A1 = T<1, 2>:
              // <Ts1..., N> is non-deduced context
              // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>:
              // <Ts2..., N> is non-deduced context
(since C++11)

9) For P of array type (but not reference to array or pointer to array), the major array bound:

template<int i>
void f1(int a[10][i]);
 
template<int i>
void f2(int a[i][20]);    // P = int[i][20], array type
 
template<int i>
void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array
 
void g()
{
    int a[10][20];
    f1(a);     // OK: deduced i = 20
    f1<20>(a); // OK
    f2(a);     // error: i is non-deduced context
    f2<10>(a); // OK
    f3(a);     // OK: deduced i = 10
    f3<10>(a); // OK
}

In any case, if any part of a type name is non-deduced, the entire type name is non-deduced context. However, compound types can include both deduced and non-deduced type names. For example, in A<T>::B<T2>, T is non-deduced because of rule #1 (nested name specifier), and T2 is non-deduced because it is part of the same type name, but in void(*f)(typename A<T>::B, A<T>), the T in A<T>::B is non-deduced (because of the same rule), while the T in A<T> is deduced.

[edit] Deduction from a type

Given a function parameter P that depends on one or more type template parameters Ti, template template parameters TTi, or non-type template parameters Ii, and the corresponding argument A, deduction takes place if P has one of the following forms:

  • cv(optional) T;
  • T*;
  • T&;
  • T(optional) [I(optional)];
  • T(optional) (U(optional));
(until C++17)
  • T(optional) (U(optional)) noexcept(I(optional));
(since C++17)
  • T(optional) U(optional)::*;
  • TT(optional)<T>;
  • TT(optional)<I>;
  • TT(optional)<TU>;
  • TT(optional)<>.

In the above forms,

  • T(optional) or U(optional) represents a type or parameter-type-list that either satisfies these rules recursively, is a non-deduced context in P or A, or is the same non-dependent type in P and A.
  • TT(optional) or TU(optional) represents either a class template or a template template parameter.
  • I(optional) represents an expression that either is an I, is value-dependent in P or A, or has the same constant value in P and A.
  • noexcept(I(optional)) represents an exception specification in which the possibly-implicit noexcept specifier’s operand satisfies the rules for an I(optional) above.
(since C++17)

If P has one of the forms that include a template parameter list <T> or <I>, then each element Pi of that template argument list is matched against the corresponding template argument Ai of its A. If the last Pi is a pack expansion, then its pattern is compared against each remaining argument in the template argument list of A. A trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack.

If P has one of the forms that include a function parameter list (T), then each parameter Pi from that list is compared with the corresponding argument Ai from A‘s function parameter list. If the last Pi is a pack expansion, then its declarator is compared with each remaining Ai in the parameter type list of A.

Forms can be nested and processed recursively:

  • X<int>(*)(char[6]) is an example of T*, where T is X<int>(char[6]);
  • X<int>(char[6]) is an example of T(optional) (U(optional)), where T is X<int> and U is char[6];
(until C++17)
  • X<int>(char[6]) is an example of T(optional) (U(optional)) noexcept(I(optional)), where T is X<int>, U is char[6], and I in the implicit noexcept specifier is false;
(since C++17)
  • X<int> is an example of TT(optional)<T>, where TT is X and T is int, and
  • char[6] is an example of T(optional) [I(optional)], where T is char and I is std::size_t(6).

Type template argument cannot be deduced from the type of a non-type template argument:

template<typename T, T i>
void f(double a[10][i]);
 
double v[10][20];
f(v); // P = double[10][i], A = double[10][20]:
      // i can be deduced to equal 20
      // but T cannot be deduced from the type of i
(until C++17)

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value.

template<long n>
struct A {};
 
template<class T>
struct C;
 
template<class T, T n>
struct C<A<n>> { using Q = T; };
 
typedef long R;
 
typedef C<A<2>>::Q R; // OK: T was deduced to long 
                      // from the template argument value in the type A<2>
 
template<auto X>
class bar{};
 
template<class T, T n>
void f(bar<n> x);
 
f(bar<3>{}); // OK: T was deduced to int (and n to 3)
             // from the template argument value in the type bar<3>

The type of N in the type T[N] is std::size_t.

template<class T, T i>
void f(int (&a)[i]);
 
int v[10];
f(v); // OK: T is std::size_t

The type of B in noexcept(B) specifier of a function type is bool.

template<bool>
struct A {};
 
template<auto>
struct B;
template<auto X, void (*F)() noexcept(X)>
struct B<F> {
    A<X> ax;
};
 
void f_nothrow() noexcept;
B<f_nothrow> bn;   // OK: X is deduced as true and the type of X is deduced as bool.
(since C++17)

If a non-type template parameter of function template is used in the template parameter list of function parameter (which is also a template), and the corresponding template argument is deduced, the type of the deduced template argument ( as specified in its enclosing template parameter list, meaning references are preserved) must match the type of the non-type template parameter exactly, except that cv-qualifiers are dropped, and except where the template argument is deduced from an array bound—in that case any integral type is allowed, even bool though it would always become true:

template<int i>
class A {};
 
template<short s>
void f(A<s>); // the type of the non-type template param is short
 
void k1()
{
    A<1> a;  // the type of the non-type template param of a is int
 
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // error: deduced non-type template argument does not have the same
             // type as its corresponding template argument
 
    f<1>(a); // OK: the template argument is not deduced, 
             // this calls f<(short)1>(A<(short)1>)
}
 
template<int&>
struct X;
 
template<int& R>
void k2(X<R>&);
 
int n;
void g(X<n> &x)
{
    k2(x); // P = X<R>, A = X<n>
           // parameter type is int&
           // argument type is int& in struct X's template declaration
           // OK (with CWG 2091): deduces R to refer to n
}

Type template parameter cannot be deduced from the type of a function default argument:

template<typename T>
void f(T = 5, T = 7);
 
void g()
{
    f(1);     // OK: calls f<int>(1, 7)
    f();      // error: cannot deduce T
    f<int>(); // OK: calls f<int>(5, 7)
}

Deduction of template template parameter can use the type used in the template specialization used in the function call:

template<template<typename> class X>
struct A {}; // A is a template with a TT param
 
template<template<typename> class TT>
void f(A<TT>) {}
 
template<class T>
struct B {};
 
A<B> ab;
f(ab); // P = A<TT>, A = A<B>: deduced TT = B, calls f(A<B>)

[edit] Other contexts

Besides function calls and operator expressions, template argument deduction is used in the following situations:

auto type deduction

Template argument deduction is used in declarations of variables, when deducing the meaning of the auto specifier from the variable’s initializer.

The parameter P is obtained as follows: in T, the declared type of the variable that includes auto, every occurrence of auto is replaced with an imaginary type template parameter U or, if the initialization is copy-list-initialization, with std::initializer_list<U>. The argument A is the initializer expression. After deduction of U from P and A following the rules described above, the deduced U is substituted into P to get the actual variable type:

const auto& x = 1 + 2; // P = const U&, A = 1 + 2:
                       // same rules as for calling f(1 + 2) where f is
                       // template<class U> void f(const U& u)
                       // deduced U = int, the type of x is const int&
 
auto l = {13}; // P = std::initializer_list<U>, A = {13}:
               // deduced U = int, the type of l is std::initializer_list<int>

In direct-list-initialization (but not in copy-list-initialization), when deducing the meaning of the auto from a braced-init-list, the braced-init-list must contain only one element, and the type of auto will be the type of that element:

auto x1 = {3}; // x1 is std::initializer_list<int>
auto x2{1, 2}; // error: not a single element
auto x3{3};    // x3 is int
               // (before N3922 x2 and x3 were both std::initializer_list<int>)
(since C++11)

auto-returning functions

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function’s return type, from the return statement.

For auto-returning functions, the parameter P is obtained as follows: in T, the declared return type of the function that includes auto, every occurrence of auto is replaced with an imaginary type template parameter U. The argument A is the expression of the return statement, and if the return statement has no operand, A is void(). After deduction of U from P and A following the rules described above, the deduced U is substituted into T to get the actual return type:

auto f() { return 42; } // P = auto, A = 42:
                        // deduced U = int, the return type of f is int

If such function has multiple return statements, the deduction is performed for each return statement. All the resulting types must be the same and become the actual return type.

If such function has no return statement, A is void() when deducing.

Note: the meaning of decltype(auto) placeholder in variable and function declarations does not use template argument deduction.

(since C++14)

[edit] Overload resolution

Template argument deduction is used during overload resolution, when generating specializations from a candidate template function.
P and A are the same as in a regular function call:

std::string s;
std::getline(std::cin, s);
 
// "std::getline" names 4 function templates, 
// 2 of which are candidate functions (correct number of parameters)
 
// 1st candidate template:
// P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// deduction determines the type template parameters CharT, Traits, and Allocator
// specialization std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// 2nd candidate template:
// P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// deduction determines the type template parameters CharT, Traits, and Allocator
// specialization std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// overload resolution ranks reference binding from lvalue std::cin
// and picks the first of the two candidate specializations

If deduction fails, or if deduction succeeds, but the specialization it produces would be invalid (for example, an overloaded operator whose parameters are neither class nor enumeration types), the specialization is not included in the overload set, similar to SFINAE.

[edit] Address of an overload set

Template argument deduction is used when taking an address of an overload set, which includes function templates.

The function type of the function template is P. The target type is the type of A:

std::cout << std::endl;
 
// std::endl names a function template
// type of endl P =
// std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&)
// operator<< parameter A =
// std::basic_ostream<char, std::char_traits<char>>& (*)(
//   std::basic_ostream<char, std::char_traits<char>>&
// )
// (other overloads of operator<< are not viable) 
// deduction determines the type template parameters CharT and Traits

An additional rule is applied to the deduction in this case: when comparing function parameters Pi and Ai, if any Pi is an rvalue reference to cv-unqualified template parameter (a «forwarding reference») and the corresponding Ai is an lvalue reference, then Pi is adjusted to the template parameter type (T&& becomes T).

If the return type of the function template is a placeholder (auto or decltype(auto)), that return type is a non-deduced context and is determined from the instantiation.

(since C++14)

[edit] Partial ordering

Template argument deduction is used during partial ordering of overloaded function templates

[edit] Conversion function template

Template argument deduction is used when selecting user-defined conversion function template arguments.

A is the type that is required as the result of the conversion. P is the return type of the conversion function template. If P is a reference type, then the referred type is used in place of P for the following parts of the section.

If A is not a reference type:

a) if the P is an array type, then the pointer type obtained by array-to-pointer conversion is used in place of P;

b) if the P is a function type, then the function pointer type obtained by function-to-pointer conversion is used in place of P;

c) if P is cv-qualified, the top-level cv-qualifiers are ignored.

If A is cv-qualified, the top-level cv-qualifiers are ignored. If A is a reference type, the referred type is used by deduction.

If the usual deduction from P and A (as described above) fails, the following alternatives are additionally considered:

a) if A is a reference type, A can be more cv-qualified than the deduced A;

b) if A is a pointer or pointer to member type, the deduced A is allowed to be any pointer that can be converted to A by qualification conversion:

struct A
{
    template<class T>
    operator T***();
};
A a;
 
const int* const* const* p1 = a;
 
// P = T***, A = const int* const* const*
// regular function-call deduction for 
// template<class T> void f(T*** p) as if called with the argument
// of type const int* const* const* fails
// additional deduction for conversion functions determines T = int 
// (deduced A is int***, convertible to const int* const* const*)

c) if A is a function pointer type, the deduced A is allowed to be pointer to noexcept function, convertible to A by function pointer conversion;

d) if A is a pointer to member function, the deduced A is allowed to be a pointer to noexcept member function, convertible to A by function pointer conversion.

(since C++17)

See member template for other rules regarding conversion function templates.

[edit] Explicit instantiation

Template argument deduction is used in explicit instantiations, explicit specializations, and those friend declarations where the declarator-id happens to refer to a specialization of a function template (for example, friend ostream& operator<< <> ()), if not all template arguments are explicitly specified or defaulted, template argument deduction is used to determine which template’s specialization is referred to.

P is the type of the function template that is being considered as a potential match, and A is the function type from the declaration. If there are no matches or more than one match (after partial ordering), the function declaration is ill-formed:

template<class X>
void f(X a);        // 1st template f
template<class X>
void f(X* a);       // 2nd template f
template<>
void f<>(int* a) {} // explicit specialization of f
 
// P1 = void(X), A1 = void(int*): deduced X = int*, f<int*>(int*)
// P2 = void(X*), A2 = void(int*): deduced X = int, f<int>(int*)
// f<int*>(int*) and f<int>(int*) are then submitted to partial ordering
// which selects f<int>(int*) as the more specialized template

An additional rule is applied to the deduction in this case: when comparing function parameters Pi and Ai, if any Pi is an rvalue reference to cv-unqualified template parameter (a «forwarding reference») and the corresponding Ai is an lvalue reference, then Pi is adjusted to the template parameter type (T&& becomes T).

[edit] Deallocation function template

Template argument deduction is used when determining if a deallocation function template specialization matches a given placement form of operator new.

P is the type of the function template that is being considered as a potential match, and A is the function type of the deallocation function that would be the match for the placement operator new under consideration. If there is no match or more than one match (after overload resolution), the placement deallocation function is not called (memory leak may occur):

struct X
{
    X() { throw std::runtime_error(""); }
 
    static void* operator new(std::size_t sz, bool b)   { return ::operator new(sz); }
    static void* operator new(std::size_t sz, double f) { return ::operator new(sz); }
 
    template<typename T>
    static void operator delete(void* ptr, T arg)
    {
        ::operator delete(ptr);
    }
};
 
int main()
{
    try
    {
        X* p1 = new (true) X; // when X() throws, operator delete is looked up
                              // P1 = void(void*, T), A1 = void(void*, bool):
                              // deduced T = bool
                              // P2 = void(void*, T), A2 = void(void*, double):
                              // deduced T = double
                              // overload resolution picks operator delete<bool>
    }
    catch(const std::exception&) {}
 
    try
    {
        X* p1 = new (13.2) X; // same lookup, picks operator delete<double>
    }
    catch(const std::exception&) {}
}

[edit] Alias templates

Alias templates are not deduced , except in class template argument deduction (since C++20):

template<class T>
struct Alloc {};
 
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
 
template<template<class, class> class TT>
void g(TT<int, Alloc<int>>);
g(v); // OK: deduced TT = vector
 
template<template<class> class TT>
void f(TT<int>);
f(v); // error: TT cannot be deduced as "Vec" because Vec is an alias template

[edit] Implicit conversions

Type deduction does not consider implicit conversions (other than type adjustments listed above): that’s the job for overload resolution, which happens later. However, if deduction succeeds for all parameters that participate in template argument deduction, and all template arguments that aren’t deduced are explicitly specified or defaulted, then the remaining function parameters are compared with the corresponding function arguments. For each remaining parameter P with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted to P, deduction fails.

Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments will be checked during overload resolution:

template<class T>
struct Z { typedef typename T::x xx; };
 
template<class T>
typename Z<T>::xx f(void*, T); // #1
 
template<class T>
void f(int, T);                // #2
 
struct A {} a;
 
int main()
{
    f(1, a); // for #1, deduction determines T = struct A, but the remaining argument 1
             // cannot be implicitly converted to its parameter void*: deduction fails
             // instantiation of the return type is not requested
             // for #2, deduction determines T = struct A, and the remaining argument 1
             // can be implicitly converted to its parameter int: deduction succeeds
             // the function call compiles as a call to #2 (deduction failure is SFINAE)
}

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 70 C++98 whether array bounds would be deduced was not specified specified as non-deduced
CWG 300 C++98 deduction took place for function parameters of form
type(*)(T)/T(*)()/T(*)(T), function pointers
match these forms but function references do not
change these forms to
type(T)/T()/T(T) so they
can also cover references
CWG 322 C++98 type parameters of reference types were not
adjusted to use the referenced type for deduction
adjustment added
CWG 976 C++98 in the deduction for conversion operator templates,
const T& return type could never match T result type
rules adjusted to
allow such matches
CWG 1387 C++11 the expression of a decltype-specifier was not a non-deduced context it is
CWG 1391 C++98 effect of implicit conversions of the arguments
that aren’t involved in deduction were not specified
specified as described above
CWG 1591 C++11 cannot deduce array bound and element type from a braced-init-list deduction allowed
CWG 2052 C++98 deducing an operator with non-class
non-enum arguments was a hard error
soft error if there
are other overloads
CWG 2091 C++98 deducing a reference non-type parameter did not
work due to type mismatch against the argument
type mismatch avoided
N3922 C++11 direct-list-initialization of auto deduces std::initializer_list ill-formed for more than one
elements, deduce element
type for single element
CWG 2355 C++17 value in a noexcept specifier of a function type was not deducible made deducible

Article catalog

Error code error reason solution

Error code

We want to implement a generic function to find the maximum value of the interval. The parameters are left and right iterators, which return the maximum value.

#include<iostream>
#include<vector>
using namespace std;
template<class T>
T findmax(typename vector<T>::iterator left,typename vector<T>::iterator right){
    T ret = *left;
    for (;left!=right;++left){
        ret = (*left)>ret ?*left : ret;     
    }
    return ret;
}

int main(){
    vector<int> a({3,4,6,2,1,5});
    cout << findmax(a.begin(),a.end());
    return 0;
}

error:
15:38: error: no matching function for call to ‘findmax(std::vector::iterator, std::vector::iterator)’
15 | cout << findmax(a.begin(),a.end());
5:3: note: candidate: ‘template T findmax(typename std::vector::iterator, typename std::vector::iterator)’
5 | T findmax(typename vector::iterator left,typename vector::iterator right){
5:3: note: template argument deduction/substitution failed:
15:38: note: couldn’t deduce template parameter ‘T’
15 | cout << findmax(a.begin(),a.end());

The reason
The compiler cannot guess that the template T is int based on the type vector< int >::iterator passed in

Solution
Specify the template manually at the place where the function is called.
Replace the used findmax with findmax< int >:

#include<iostream>
#include<vector>
using namespace std;
template<class T>
T findmax(typename vector<T>::iterator left,typename vector<T>::iterator right){
    T ret = *left;
    for (;left!=right;++left){
        ret = (*left)>ret ?*left : ret;     
    }
    return ret;
}

int main(){
    vector<int> a({3,4,6,2,1,5});
    cout << findmax<int>(a.begin(),a.end());
    return 0;
}

Run successfully

Read More:

Я хотел создать простой вспомогательный алгоритм, который заполнил бы контейнер, например std::vector<T>, с геометрической прогрессией (первый член равен a, А n-й член определяется выражением a * pow(r, n-1), Где r заданное отношение); Я создал следующий код:

#include<vector>
#include<algorithm>
#include<iostream>

template<template <typename> class Container, typename T>
void progression(Container<T>& container, T a, T ratio, size_t N) {
  if(N > 0) {    
    T factor = T(1);  
    for(size_t k=0; k<N; k++) {
      container.push_back(a * factor);
      factor *= ratio;
    }
  }
}

int main() {
  std::vector<double> r;
  progression(r, 10.0, 0.8, static_cast<size_t>(10));

  for(auto item : r) {
    std::cout<<item<<std::endl;
  }

  return 0;
}

который дает следующие ошибки при попытке компиляции:

$ g++ geometric.cpp -std=c++11 # GCC 4.7.2 on OS X 10.7.4
geometric.cpp: In function ‘int main()’:
geometric.cpp:18:52: error: no matching function for call to ‘progression(std::vector<double>&, double, double, size_t)’
geometric.cpp:18:52: note: candidate is:
geometric.cpp:6:6: note: template<template<class> class Container, class T> void progression(Container<T>&, T, T, size_t)
geometric.cpp:6:6: note:   template argument deduction/substitution failed:
geometric.cpp:18:52: error: wrong number of template arguments (2, should be 1)
geometric.cpp:5:36: error: provided for ‘template<class> class Container’

Сообщение об ошибке Clang более тонкое:

$ clang++ geometric.cpp -std=c++11 # clang 3.2 on OS X 10.7.4 
geometric.cpp:18:3: error: no matching function for call to 'progression'
  progression(r, 10, 0.8, 10);
  ^~~~~~~~~~~
geometric.cpp:6:6: note: candidate template ignored: failed template argument deduction
void progression(Container<T>& container, T a, T ratio, size_t N) {
     ^
1 error generated.

Я ожидал, что с помощью параметров шаблона шаблона я смогу вывести не только контейнер, но и контейнер value_type (T в этом случае).

Итак, вопрос: как я могу создать общую функцию, которая сможет вывести как тип контейнера, так и тип значения?

Я уверен, что упускаю что-то очевидное — я ценю ваше терпение и помощь.

Изменить (ответ)

Следующий код ведет себя так, как ожидалось:

#include<vector>
#include<algorithm>
#include<iostream>

template<template <typename...> class Container, typename T, typename... Args>
void progression(Container<Args...>& container, T a, T ratio, size_t N) {
  if(N > 0) {    
    T factor = T(1);  
    for(size_t k=0; k<N; k++) {
      container.push_back(a * factor);
      factor *= ratio;
    }
  }
}

int main() {
  std::vector<double> r;
  progression(r, 10.0, 0.8, 10);

  for(auto item : r) {
    std::cout<<item<<std::endl;
  }

  return 0;
}

Вывод:

10
8
6.4
5.12
4.096
3.2768
2.62144
2.09715
1.67772
1.34218

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Error class ntpclient has no member named getformatteddate
  • Error certificate has expired next rp
  • Error certificate common name
  • Error class node js
  • Error certificate authority not found

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии