Skip to content

Latest commit

 

History

History
799 lines (599 loc) · 43.6 KB

base.md

File metadata and controls

799 lines (599 loc) · 43.6 KB
  • Общие вопросы

➕ C/C++

const

Функция

  1. Изменение переменной, указывающее на то, что переменная не может быть изменена;
  2. Измененные указатели, разделенные на указатели на const (указатель на const) и указатели, которые сами являются константами (const указатель, const указатель);
  3. Измененные ссылки, ссылки на константы (ссылка на const), используются для типов формальных параметров, что позволяет избежать копирования и модификации функции значений;
  4. Украшение функции-члена, указывающее на то, что переменные-члены не могут быть изменены внутри функции-члена.

Const Указатели и ссылки

  • Указатель
    • Указатель на const
    • Указатель на саму константу (const указатель)
  • Ссылка
    • Ссылка на const
    • Нет const ссылки, потому что ссылка является псевдонимом объекта, ссылка не является объектом

(Думайте об этом для удобства) Значение, измененное const (после const), не может быть изменено, например p2, p3 в примере использования ниже

использование

const использование

    // класс
    class A
    {
    private:
        const int a;                // константный объект-член, может использовать список инициализации или инициализатор в классе
    
    public:
        // Конструктор
        A() : a(0) { };
        A(int x) : a(x) { };        //  список инициализации
    
        //  const может использоваться для различения перегруженных функций
        int getValue();             //  обычная функция-член
        int getValue() const;       // константная функция-член, не должна изменять значение любого члена данных в классе
    };
    
    void function()
    {
        // объект
        A b;                        // обычный объект, может вызывать все функции-члены
        const A a;                  // константный объект, может вызывать только константные функции-члены
        const A *p = &a;            // переменная указателя, указывает на константный объект
        const A &q = a;             // ссылка на константный объект
    
        // указатель
        char greeting[] = "Hello";
        char* p1 = greeting;                // переменная указателя, указывающая на переменную массива символов
        const char* p2 = greeting;          // переменная указателя, указывающая на константу массива символов (char, за которым следует const, указывает на то, что указываемый символ (char) не может быть изменен)
        char* const p3 = greeting;          // сам по себе является константным указателем на переменную массива символов (const, за которым следует p3, указывает на то, что сам указатель p3 не может быть изменен)
        const char* const p4 = greeting;    // указатель на саму константу, указывающий на константу массива символов
    }
    
    // функция
    void function1(const int Var);           // переданные параметры являются неизменными внутри функции
    void function2(const char* Var);         // Содержимое, на которое указывает параметр указателя, является константой
    void function3(char* const Var);         // параметр указателя является константой
    void function4(const int& Var);          // ссылочный параметр является константой внутри функции
    
    // возвращаемое значение функции
    const int function5();      // возвращает константу
    const int* function6();     // возвращает переменную указателя на константу, используйте: const int * p = function6 ();
    int* const function7();     // возвращает константный указатель на переменную, используйте: int * const p = function7 ();

#define и const константы

#define const константы
Макроопределения, эквивалентные замене символов декларации констант
предварительная обработка обработка компилятора
без проверки типов безопасности с проверкой типов безопасности
без выделения памяти требуется выделение памяти
хранится в сегменте кода хранится в сегменте данных
Может быть отменено с помощью #undef Не отменяемый

static

Функция

  1. Изменение обычных переменных, изменение области хранения и жизненного цикла переменных, делает переменные, хранящиеся в статической области, выделяет пространство перед запуском функции main, если есть начальное значение, инициализирует его начальным значением, если нет начального значения, система использует значение по умолчанию для его инициализации.
  2. Изменение обычных функций для указания области действия функции, которая может использоваться только в файле, где определена функция. При разработке проекта с несколькими людьми, чтобы предотвратить дублирование имен от функций в пространствах имен других людей, вы можете позиционировать функции как статические.
  3. Декорирование переменных-членов. Декорирование переменных-членов так, что все объекты содержат только одну из переменных, и вы можете получить доступ к члену без создания объекта.
  4. Декорирование функций-членов. Декорирование функций-членов так, чтобы к ним можно было получить доступ без создания объектов, но в статических функциях не могут быть доступны нестатические члены.

указатель this

  1. Указатель this - это специальный указатель, скрытый в каждой нестатической функции-члене. Он указывает на объект, который вызвал функцию-член.
  2. При вызове функции-члена на объекте компилятор сначала присваивает адрес объекта указателю this, а затем вызывает функцию-член. Каждый раз, когда функция-член обращается к члену данных, неявно используется указатель this.
  3. Когда вызывается функция-член, автоматически передается неявный параметр, который является указателем на объект, где находится функция-член.
  4. Указатель this неявно объявлен как: ClassName * const this, что означает, что указатель this не может быть присвоен; в const функции-члене класса ClassName тип указателя this для: const ClassName * const, это означает, что объект, на который указывает указатель this, не может быть изменен (то есть данные члены таких объектов не могут быть присвоены);
  5. this - это не обычная переменная, а rvalue, поэтому вы не можете получить адрес this (вы не можете & this).
  6. Часто необходимо явно ссылаться на указатель this в следующих сценариях:
    1. Для реализации цепочечной ссылки на объект;
    2. Чтобы избежать выполнения присваиваний на одном и том же объекте;
    3. При реализации некоторых структур данных, таких как list.

inline функция

Особенности

  • Эквивалентно записи содержимого inline функции при вызове inline функции;
  • Эквивалентно прямому выполнению тела функции без выполнения шагов входа в функцию;
  • Эквивалентно макросу, но с большей проверкой типов, чем у макроса, он действительно имеет функциональные характеристики;
  • Компилятор обычно не встраивает inline функции, которые включают сложные операции, такие как циклы, рекурсия и переключение;
  • Функции, определенные в объявлениях классов, кроме виртуальных функций, автоматически неявно рассматриваются как inline функции.

использование

inline использование

// Утверждение 1 (плюс inline, рекомендуется)
inline int functionName(int first, int second,...);

// утверждение 2 (без inline)
int functionName(int first, int second,...);

// определение
inline int functionName(int first, int second,...) {/****/};

// внутри определения класса, неявно inline
class A {
    int doA() { return 0; }         // неявное встраивание
}

// определение вне класса, необходимо явно встроить
class A {
    int doA();
}
inline int A::doA() { return 0; }   // требуется явное встраивание

Этапы обработки компилятора для inline функций

  1. Скопировать тело inline функции в точку вызова inline функции;
  2. Выделить пространство памяти для локальных переменных в используемой inline функции;
  3. Сопоставить входные параметры и возвращаемые значения inline функции с пространством локальных переменных вызывающего метода;
  4. Если у inline функции есть несколько точек возврата, превратите ее в ветвь в конце блока кода inline функции ( используя GOTO).

Преимущества и недостатки

Преимущества

  1. Inline функции, как и макрофункции, выполняют расширение кода на месте вызывающего, устраняя необходимость в толчении параметров на стек, открытии и восстановлении стековых кадров и возвращении результатов и т. д., тем самым улучшая скорость выполнения программы.
  2. По сравнению с макрофункциями, inline функции выполняют проверку безопасности или автоматическое преобразование типов (как и обычные функции) при расширении кода, в то время как макроопределения этого не делают.
  3. Объявление функции-члена, которое также определено в классе, автоматически преобразует его в inline функцию, поэтому inline функции могут получить доступ к переменным-членам класса, в то время как макроопределения не могут.
  4. Inline функции можно отладить во время выполнения, в то время как макроопределения - нет.

Недостатки

  1. Раздутие кода. Встраивание происходит за счет раздутия кода (копирования), устраняя накладные расходы на вызов функций. Если время выполнения кода в теле функции больше, чем стоимость вызова функции, то прирост эффективности будет небольшим. С другой стороны, копирование кода для каждого вызова inline функции увеличит общий размер кода программы и потребует больше памяти.
  2. Inline функцию нельзя обновить с обновлением библиотеки функций. Изменения в inline функции требуют повторной компиляции, в отличие от не-inline, которую можно прямо связать.
  3. Является ли она inline или нет, находится за пределами контроля программиста. Inline функции - это просто предложения компилятору. Решение о встраивании функций принимает компилятор.

Может ли виртуальная функция быть inline функцией?

Are "inline virtual" member functions ever actually "inlined"?

  • Виртуальная функция может быть inline функцией. Inline может изменить виртуальную функцию, но она не может быть встроена, когда виртуальная функция проявляет полиморфизм.
  • Встраивание рекомендуется компилятором, а полиморфизм виртуальных функций происходит во время выполнения. Компилятор не может знать, какой код вызывается во время выполнения, поэтому виртуальные функции не могут быть встроены во время выполнения (runtime).
  • inline virtual Единственное время, когда она может быть встроена: компилятор знает, какой класс вызывает объект ( например, Base::who() ), только если у компилятора есть фактический объект, а не указатель или ссылка на объект, произойдет.

Использование виртуальной функции inline

#include <iostream>  
using namespace std;
class Base
{
public:
	inline virtual void who()
	{
		cout << "Я Base\n";
	}
	virtual ~Base() {}
};
class Derived : public Base
{
public:
	inline void who()  // Неявное встраивание при не написании inline
	{
		cout << "Я Derived\n";
	}
};

int main()
{
	// Здесь виртуальная функция who () вызывается через конкретный объект (b) класса (Base), который может быть определен во время компиляции, поэтому он может быть встроен, но будет ли он встроен, зависит от компиляции Устройство.
	Base b;
	b.who();

	// Здесь виртуальная функция вызывается через указатель, который является полиморфным и должен быть определен во время выполнения, поэтому он не может быть встроен.
	Base *ptr = new Derived();
	ptr->who();

	// Поскольку Base имеет виртуальный деструктор (virtual ~ Base () {}), при удалении сначала вызывается деструктор Derived, а затем деструктор Base, чтобы предотвратить утечки памяти.
	delete ptr;
	ptr = nullptr;

	system("pause");
	return 0;
} 

volatile

volatile int i = 10; 
  • Ключевое слово volatile является модификатором типа, и переменная типа, объявленная с его помощью, указывает, что она может быть изменена некоторыми факторами, неизвестными компилятору (операционная система, аппаратное обеспечение, другие потоки и т. д.). Таким образом, использование volatile говорит компилятору, что такие объекты не следует оптимизировать.
  • Переменные, объявленные с помощью ключевого слова volatile, должны быть извлечены из памяти при каждом доступе к ним ( переменные, которые не изменяются с помощью volatile, могут быть извлечены из регистров CPU из-за оптимизации компилятора)
  • const может быть volatile (например, регистр состояния только для чтения)
  • Указатель может быть volatile

assert()

Утверждения являются макросами, а не функциями. Прототип макроса assert определен в <assert.h> (C), <cassert> ( C ++), и его роль заключается в прекращении выполнения программы, если его условие возвращает ошибку. Вы можете отключить assert, определив NDEBUG, но это должно быть в начале исходного кода, перед include <assert.h> .

assert () использует

#define NDEBUG          // Добавьте эту строку, assert не доступен
#include <assert.h>

assert( p != NULL );    // assert не доступен

sizeof()

  • sizeof для массивов - получить размер всего массива.
  • sizeof для указателей - получить размер пространства, занимаемого самим указателем.

#pragma pack(n)

Установите структуру, объединение и переменные-члены класса, чтобы быть выровненными по n-байтам

#pragma pack (n) использует

#pragma pack(push)  // сохранить состояние выравнивания
#pragma pack(4)     // Установить 4-байтовое выравнивание

struct test
{
    char m1;
    double m4;
    int m3;
};

#pragma pack(pop)   // Восстановить выравнивание

Поле битов

Bit mode: 2;    // mode состоит из 2 цифр

Класс может определить свои (нестатические) данные-члены в виде полей битов, которые содержат определенное количество двоичных битов в поле битов. Когда программа должна передать двоичные данные другим программам или аппаратным устройствам, обычно используется поле битов.

  • Расположение поля битов в памяти зависит от машины
  • Тип поля битов должен быть целым или перечисляемым типом. Поведение поля битов в знаковом типе будет зависеть от реализации.
  • Оператор извлечения (&) не может быть применен к полю битов, и ни один указатель не может указывать на поле битов класса

extern "C"

  • Функции или переменные с квалификатором extern имеют тип extern
  • Переменные и функции, декорированные с помощью extern" C ", компилируются и связываются в C

Функция extern" C " заключается в том, чтобы позволить компилятору C ++ рассматривать код, объявленный с помощью extern" C ", как код на языке C, что может избежать проблемы, что код не может быть связан с символами в библиотеке языка C из-за модификации символов. .

extern "C" демо

#ifdef __cplusplus
extern "C" {
#endif

void *memset(void *, int, size_t);

#ifdef __cplusplus
}
#endif

struct с typedef struct

В C

// c
typedef struct Student {
    int age; 
} S;

Эквивалентно

// c
struct Student { 
    int age; 
};

typedef struct Student S;

В этот момент S эквивалентен struct Student, но два идентификатора пространства имен различны.

Вы также можете определить void Student(){}, который не конфликтует с struct Student .

На C++

Правила компилятора для размещения символов (правила поиска) изменились, поэтому они отличаются от языка C.

Во-первых, если определено struct Student {...}; в пространстве идентификаторов класса, при использовании Student me; компилятор будет искать в глобальной таблице идентификаторов. Если Student не найден, он будет искать в идентификаторе класса.

Это означает, что можно использовать Student или struct Student, как показано ниже:

// cpp
struct Student { 
    int age; 
};

void f( Student me );       // верно, ключевое слово "struct" можно опустить

Если определена функция с тем же именем, что и Student, Student представляет только функцию, а не структуру, как показано ниже:

typedef struct Student { 
    int age; 
} S;

void Student() {}           //Верно, "Student" представляет только эту функцию после определения

//void S() {}               // Ошибка, символ "S" уже определен как псевдоним для "struct Student"

int main() {
    Student(); 
    struct Student me;      // Или "S me";
    return 0;
}

struct и class в C ++

В общем, struct больше подходит для реализации структуры данных, а class больше подходит для реализации объекта.

Разница

  • Самое существенное отличие - это контроль доступа по умолчанию
    1. Наследуемые права доступа по умолчанию. У struct они публичные, а у class - приватные.
    2. struct как тело реализации структуры данных, его контроль доступа к данным по умолчанию является публичным, а class как тело реализации объекта, его контроль доступа к переменным-членам по умолчанию является приватным.

union union

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

  • Символ контроля доступа по умолчанию - public
  • Может содержать конструкторы и деструкторы
  • Не может содержать членов типа ссылки
  • Не может наследоваться от других классов и не может использоваться в качестве базового класса
  • Не может содержать виртуальных функций
  • Анонимный union может напрямую обращаться к членам union в области, где он определен
  • Анонимный union не может содержать защищенных членов или частных членов
  • Глобальный анонимный union должен быть статическим

демо union

#include<iostream>

union UnionTest {
    UnionTest() : i(10) {};
    int i;
    double d;
};

static union {
    int i;
    double d;
};

int main() {
    UnionTest u;

    union {
        int i;
        double d;
    };

    std::cout << u.i << std::endl;  // Вывод UnionTest union 10

    ::i = 20;
    std::cout << ::i << std::endl;  // Вывод глобального статического анонимного union 20

    i = 30;
    std::cout << i << std::endl;    // Вывод локального анонимного union 30

    return 0;
}

C реализует классы C ++

C реализует объектно-ориентированные особенности C ++ (инкапсуляция, наследование, полиморфизм)

  • Инкапсуляция: Используйте указатели на функции для инкапсуляции свойств и методов в структурах
  • Наследование: вложенность структур
  • Полиморфизм: указатели на функции родительских и дочерних методов отличаются

Можно ли написать объектно-ориентированный код на C? [закрыто]

explicit (ключевое слово)

  • Модификация конструктора explicit предотвращает неявное преобразование и инициализацию копированием
  • Явные преобразования могут предотвратить неявные преобразования, за исключением преобразования по контексту

демо explicit

struct A
{
	A(int) { }
	operator bool() const { return true; }
};

struct B
{
	explicit B(int) {}
	explicit operator bool() const { return true; }
};

void doA(A a) {}

void doB(B b) {}

int main()
{
	A a1(1);		// OK: прямая инициализация
	A a2 = 1;		// OK: инициализация копированием
	A a3{ 1 };		// OK: прямая инициализация списка
	A a4 = { 1 };		// OK: инициализация копированием списка
	A a5 = (A)1;		// OK: разрешено явное преобразование static_cast
	doA(1);			// OK: разрешено неявное преобразование из int в A
	if (a1);		// OK: неявное преобразование из A в bool с использованием функции преобразования A ::operator bool()
	bool a6(a1);		// OK: неявное преобразование из A в bool с использованием функции преобразования A::operator bool()
	bool a7 = a1;		// OK: неявное преобразование из A в bool с использованием функции преобразования A::operator bool()
	bool a8 = static_cast<bool>(a1);  // OK: static_cast для прямой инициализации

	B b1(1);		// OK: прямая инициализация
	B b2 = 1;		// Ошибка: объект, измененный с помощью явного конструктора, не может быть инициализирован копированием
	B b3{ 1 };		// OK: прямая инициализация списка
	B b4 = { 1 };		// Ошибка: объект, измененный с помощью явного конструктора, не может инициализировать список копированием
	B b5 = (B)1;		// OK: разрешено явное преобразование static_cast
	doB(1);			// Ошибка: объекты, конструктор которых явно изменен, не могут быть неявно преобразованы из int в B
	if (b1);		// OK: объекты, измененные с помощью явной функции преобразования B::operator bool(), могут быть преобразованы из B в bool по контексту
	bool b6(b1);		// OK: объекты, измененные с помощью явной функции преобразования B::operator bool(), могут быть преобразованы из B в bool по контексту
	bool b7 = b1;		// Ошибка: объекты, измененные с помощью явной функции преобразования B :: operator bool (), не могут быть неявно преобразованы
	bool b8 = static_cast<bool>(b1);  // OK: static_cast выполняет прямую инициализацию

	return 0;
}

friend - класс-друг и функция-друг

  • Доступ к приватным членам
  • Разрушение инкапсуляции
  • Дружба не транзитивна
  • Односторонняя дружба
  • Нет ограничений на форму и количество деклараций дружбы

using

Использование выражения

using declaration вводит только одного члена пространства имен за раз. Это позволяет нам точно знать, какое имя ссылается в программе. Например:

using namespace_name :: name;

Использование декларации конструктора

В C ++ 11 производный класс может повторно использовать конструктор, определенный его непосредственным базовым классом.

class Derived : Base {
public:
    using Base::Base;
    /* ... */
};

Как указано выше, для каждого конструктора базового класса компилятор генерирует соответствующий ему конструктор производного класса (список параметров точно такой же). Генерируется следующий тип конструктора:

Derived (parms): Base (args) {}

Инструкции using

Директива using делает видимыми все имена в определенном пространстве имен, поэтому нам не нужно добавлять к ним никаких префиксных квалификаторов. Например:

using namespace_name name;

Минимизация using directives для загрязнения пространств имен

В общем, безопаснее использовать команду using, чем компилировать команду using, потому что она импортирует только указанное имя. Если имя конфликтует с локальным именем, компилятор выдаст инструкции. Компилировать команду using импортирует все имена, включая имена, которые могут быть не нужны. Если есть конфликт с локальным именем, локальное имя переопределит версию пространства имен, и компилятор не выдаст предупреждение. Кроме того, открытость пространства имен означает, что имена пространства имен могут быть разбросаны в разных местах, что затрудняет точное знание, какие имена были добавлены.

демо using

Минимизация using directives

using namespace std;

Вы должны чаще использовать using declarations

int x;
std::cin >> x ;
std::cout << x << std::endl;

или

using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;

:: оператор разрешения области видимости

классификация

  1. Глобальная область видимости (:: name): используется перед именами типов (классы, члены класса, функции-члены, переменные и т. д.), чтобы указать, что область видимости - это глобальное пространство имен
  2. Символ области видимости класса (class :: name): используется для указания, что область видимости указанного типа относится к классу
  3. Область видимости пространства имен (namespace :: name): используется для указания, что область видимости указанного типа относится к пространству имен

демо ::

int count = 11;         // Глобальный (::) count

class A {
public:
	static int count;   // Count (A::count) класса A
};
int A::count = 21;

void fun()
{
	int count = 31;     // Инициализируем локальный count как 31
	count = 32;         // Устанавливаем локальный count как 32
}

int main() {
	::count = 12;       // Тест 1: Устанавливаем глобальный count как 12

	A::count = 22;      // Тест 2: Устанавливаем count класса A как 22

	fun();		        // Тест 3

	return 0;
}

enum - тип enum

Область перечисления

enum class open_modes { input, output, append };

Неограниченный тип перечисления

enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };

decltype

Ключевое слово decltype используется для проверки объявленного типа или типа выражения и классификации значения сущности. грамматика:

decltype ( expression )

демо decltype

// Возврат хвоста позволяет нам объявить тип возврата после списка параметров
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    / обработка последовательности
    return *beg;     // возвращаем ссылку на элемент в последовательности
}
// Чтобы использовать члены параметра шаблона, вы должны использовать typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
    // обработка последовательности
    return * beg; // возвращаем копию элемента в последовательности
}

ссылка

ссылка lvalue

Обычная ссылка, которая обычно представляет собой идентичность объекта.

ссылка rvalue

Ссылка rvalue - это ссылка, которая должна быть связана с rvalue (временным объектом, объектом, подлежащим уничтожению) и обычно представляет собой значение объекта.

Ссылка rvalue реализует Move Sementics и Perfect Forwarding. Ее основная цель двояка:

  • Устранить ненужное копирование объектов при взаимодействии двух объектов, экономя вычислительные ресурсы хранения и повышая эффективность.
  • Возможность более кратко определить обобщенные функции.

Сворачивание ссылок

  • X & &, X & &&, X && & могут быть свернуты в X &
  • X && && может быть свернут в X &&

Макрос

  • Определение макроса может реализовать функцию, похожую на функцию, но это все же не функция, и "параметры" в скобках в определении макроса - это не настоящие параметры. "Параметры" заменяются один к одному при расширении макроса.

Список инициализации членов

преимущества

  • Более эффективно: Нет необходимости вызывать конструктор по умолчанию еще раз.
  • В некоторых случаях необходимо использовать список инициализации:
    1. Константные члены, потому что константы можно только инициализировать и не могут быть присвоены, поэтому они должны быть помещены в список инициализации
    2. Типы ссылок. Ссылки должны быть инициализированы в момент определения и не могут быть переназначены, поэтому они также должны быть записаны в список инициализации.
    3. Нет класса типа для конструктора по умолчанию, потому что список инициализации можно использовать для инициализации без необходимости вызывать конструктор по умолчанию.

initializer_list список инициализации

Инициализируйте объект с помощью списка инициализаторов в фигурных скобках, где соответствующий конструктор принимает параметр std :: initializer_list.

использование initializer_list

#include <iostream>
#include <vector>
#include <initializer_list>
 
template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // Copy the list initialization in the return statement
                                   // this does not use std :: initializer_list
    }
};
 
template <typename T>
void templated_fn(T) {}
 
int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // copy initialization
    s.append({6, 7, 8});      // list initialization in function call
 
    std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";
 
    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';
 
    std::cout << "Range-for over brace-init-list: \n";
 
    for (int x : {-1, -2, -3}) // auto rules make this band for work
        std::cout << x << ' ';
    std::cout << '\n';
 
    auto al = {10, 11, 12};   // special rules for auto
 
    std::cout << "The list bound to auto has size() = " << al.size() << '\n';
 
//    templated_fn({1, 2, 3}); // Compile error! "{1, 2, 3}" is not an expression,
                              // it has no type, so T cannot infer
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // also OK
}