Коротко — идея проверки совместимости с шаблонным конструктором: явно ограничить (заклинить) то, какие типы может принять шаблонный конструктор, чтобы он не перехватывал все подряд, не ломал семантику copy/move и не давал неожиданных привидений. Практически это делается через SFINAE (std::enable_if / type traits) в старых стандартах или через requires / concepts в C++20, и обычно вы проверяете, можно ли построить ваш тип Argument из переданного аргумента (std::is_constructible / std::is_convertible).
Ниже — причины, что именно проверять, и примеры.
1) Что обычно нужно проверить
- Можно ли построить Argument из данного T: std::is_constructible<Argument, T&&>.
- is_constructible — проверяет, что конструкция допустима (включая explicit). is_convertible — проверяет, можно ли неявно преобразовать.
- Запретить шаблон захватывать копирующий/перемещающий конструктор Format: если шаблонный конструктор принимает один параметр, он может конфликтовать с copy-constructor (или hide перемещающий). Поэтому нужно SFINAE-выключить случай, когда единственный параметр — уже Format.
- Иногда нужно учесть std::initializer_list — если вы хотите поддержать/отключить list-initialization, это отдельный кейс.
2) Рекомендации
- Явно =default копирующие/перемещающие конструкторы/операторы, если нужен стандартный behaviour (пользовательский конструк-тор шаблонный считается user-declared и может подавить генерацию move).
- Проверять std::is_constructible<Argument, T&&> (или std::convertible_to в C++20, если нужна неявная конверсия).
- Для C++20 — писать requires с концептом (чисто и читаемо). Для C++11/14 — use std::enable_if_t.
3) Примеры
C++20 (рекомендую, если доступно)
```cpp
#include <type_traits>
#include <vector>
#include <utility>
struct Argument {
template<typename T>
explicit Argument(T&& v) { /* ... */ }
};
struct Format {
std::vector<Argument> args;
Format() = default;
Format(Format const&) = default;
Format(Format&&) = default;
template<typename... Ts>
requires ((std::constructible_from<Argument, Ts> && ...) &&
!(sizeof...(Ts) == 1 && std::same_as<std::decay_t<Ts>..., Format>))
Format(Ts&&... ts)
: args{ Argument(std::forward<Ts>(ts))... }
{}
};
```
Здесь:
- ((std::constructible_from<Argument, Ts> && ...)) — проверяет, что каждый Ts может быть использован для конструктора Argument.
- выражение с sizeof...(Ts) && std::same_as предотвращает попадание шаблонного конструктора в ситуацию копирования Format (один аргумент типа Format).
C++11/14 (SFINAE)
```cpp
#include <type_traits>
#include <vector>
#include <utility>
struct Argument {
template<typename T>
explicit Argument(T&& v) { /* ... */ }
};
template<bool B, class T = void> using enable_if_t = typename std::enable_if<B,T>::type;
template<typename U, typename... Ts>
struct all_constructible;
template<typename U>
struct all_constructible<U> : std::true_type {};
template<typename U, typename T0, typename... Ts>
struct all_constructible<U, T0, Ts...>
: std::integral_constant<bool,
std::is_constructible<U, T0&&>::value && all_constructible<U, Ts...>::value> {};
struct Format {
std::vector<Argument> args;
Format() = default;
Format(Format const&) = default;
Format(Format&&) = default;
template<typename... Ts, class = enable_if_t<
all_constructible<Argument, Ts...>::value &&
!(sizeof...(Ts) == 1 && std::is_same<typename std::decay<Ts>::type..., Format>::value)
>>
Format(Ts&&... ts)
: args{ Argument(std::forward<Ts>(ts))... }
{}
};
```
(в C++14 можно заменить all_constructible на std::conjunction и std::is_constructible прямо)
4) Дополнительные нюансы и советы
- is_constructible vs is_convertible: is_constructible учитывает explicit-конструкторы; is_convertible проверяет возможность неявного преобразования. Выбирайте то, что вам нужно.
- Помните про эффекты шаблонных конструкторов на генерацию move/copy: объявите =default, если хотите их сохранить.
- Делайте конструктор Argument explicit, если не хотите неявных преобразований.
- Для удобства и читаемости в C++20 используйте концепты/constraints.
- Если встречаются неоднозначности с std::initializer_list, добавьте отдельный overload или SFINAE для этого случая.
Если хотите — пришлите текущую реализацию классов Argument и Format, я покажу конкретные правки и готовый код с ограничениями, подходящими для вашего случая.