У меня есть вспомогательные классы QueryField и Select, используемые для построения операторов SQL:
class QueryField
{
public:
QueryField(std::string_view column)
: m_column{ column }
{
}
QueryField(std::string_view column, std::string_view alias)
: m_column{ column }
, m_alias{ alias }
{
}
private:
std::string m_column;
std::string m_alias;
};
class Select
{
public:
Select(std::initializer_list<QueryField> fields)
{
for (auto & field : fields)
{
m_fields.emplace_back(std::move(field));
}
}
private:
std::vector<QueryField> m_fields;
};
как видно из приведенного выше кода, Select представляет собой набор объектов QueryField, которые можно инициализировать следующим образом:
Select{ QueryField{ "up.audit_option" "option" }, QueryField("uep.success"), QueryField("uep.failure") };
можно ли исключить необходимость явного указания QueryField и инициализировать объект Select следующим образом?
Select{ { "up.audit_option" "option" }, "uep.success", "uep.failure" };
поправьте меня, если я ошибаюсь, но вам нужен однородный (одинаковые типы), а не разнородный (разные типы) список
Какой компилятор вы используете? Мне кажется, компилируется нормально? godbolt.org/z/XwgbD-? Или я что-то упускаю?
С вашим решением вы действительно можете отказаться от типов, но вы должны сохранить фигурные скобки:
Select{ { "up.audit_option" "option" }, {"uep.success"}, {"uep.failure"} }
Также будьте осторожны с инициализированным списком: все элементы внутри будут скопированы. Даже если вы переедете:
Select(std::initializer_list<QueryField> fields)
{
for (auto & field : fields)
{
// Actually copy. No move is done.
m_fields.emplace_back(std::move(field));
}
}
Никакое перемещение не разрешено, так как все элементы в списке инициализаторов являются постоянными.
Моим предпочтительным решением было бы отказаться от std::initializer_list
и быть простым с простым случаем и более явным со сложными случаями.
Чтобы разрешить настоящие разнородные параметры, я буду использовать вариативные шаблоны:
template<typename... Args>
Select(Args&&... fields) :
m_fields{QueryField{std::forward<Args>(args)}...} {}
Если вы хотите сохранить конструктор копирования/перемещения, вы должны отфильтровать некоторые типы параметров:
template<typename T, typename = void typename... Args>
struct is_not_copy_impl : std::false_type {};
template<typename T, typename Arg>
struct is_not_copy_impl<T, std::enable_if_t<std::is_base_of_v<T, std::decay_t<Arg>>>, Arg> : std::true_type {};
template<typename T, typename... Args>
using is_not_copy = is_not_copy_impl<T, void, Args...>;
template<typename... Args, std::enable_if_t<!is_not_copy<Select, Args...>::value>* = nullptr>
Select(Args&&... fields) :
m_fields{QueryField{std::forward<Args>(args)}...} {}
Этот код будет перемещаться при передаче QueryField
и создавать новый при передаче значения другого типа.
Использование таково:
Select{
QueryField{"up.audit_option" "option"},
"uep.success",
"uep.failure"
};
ИМХО конструктор с вариативным шаблоном, вероятно, лучше, но он скрывает конструкторы копирования и перемещения. Выберите select(other) и Select select(std::move(other)) останавливает компиляцию. Я объявил оба конструктора по умолчанию, но это не помогает.
Возможно, решение состоит в том, чтобы сделать его не конструктором, а функцией типа MakeSelect(Args &&... fields) (например, std::make_shared)
Ах да, вы должны отфильтровать конструктор перемещения и копирования. я отредактировал ответ
Я пытался сделать это один раз и даже спросил на ТАК, я думаю. Я почти уверен, что гетерогенные списки инициализации в настоящее время невозможны в cpp.