2

I have a QueryField and Select helper classes used to construct SQL statements:

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; }; 

as seen from the code above Select is a collection of QueryField objects that can initialized like this:

Select{ QueryField{ "up.audit_option" "option" }, QueryField("uep.success"), QueryField("uep.failure") }; 

is it possible to eliminate the need of specifying QueryField explicitly and initialize Select object as follows?

Select{ { "up.audit_option" "option" }, "uep.success", "uep.failure" }; 
3
  • I tried to do it once and even asked on SO I guess. I'm pretty sure heterogenous init lists are currently impossible in cpp Commented May 27, 2019 at 14:40
  • correct me if I'm wrong, but you want a homogeneous (same types) not a heterogeneous (different types) list Commented May 27, 2019 at 14:50
  • What compiler are you using? Seems to compile fine to me? godbolt.org/z/XwgbD-? Or there's something I'm missing? Commented May 27, 2019 at 14:54

1 Answer 1

4

With your solution, you can indeed drop the types, but you have to keep the braces:

Select{ { "up.audit_option" "option" }, {"uep.success"}, {"uep.failure"} } 

Also be careful with initialized list: all the elements inside will be copied. Even if you move:

Select(std::initializer_list<QueryField> fields) { for (auto & field : fields) { // Actually copy. No move is done. m_fields.emplace_back(std::move(field)); } } 

No move is allowed since every elements in the initializer list are constant.


My preferred solution would be to drop std::initializer_list and be simple with simple case and more explicit with complex cases.

To allow true heterogenous parameters, I'll go with variadic templates:

template<typename... Args> Select(Args&&... fields) : m_fields{QueryField{std::forward<Args>(args)}...} {} 

If you want to keep the copy/move constructor, you must filter out some parameter types:

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)}...} {} 

This code will move when a QueryField is passed, and construct a new one when a value of other type is passed.

The usage is this:

Select{ QueryField{"up.audit_option" "option"}, "uep.success", "uep.failure" }; 
Sign up to request clarification or add additional context in comments.

3 Comments

IMHO the constructor with variadic template is probably better, but it hides the copy and move constructors. Select select(other) and Select select(std::move(other)) stops compile. I declared both constructors as default but it does not help.
Probably the solution is make it not a constructor, but a function like MakeSelect(Args && ... fields) (like std::make_shared)
Ah yes you must filter out the move and copy constructor. I edited the answer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.