0

I am trying to pass as a function argument a vector of pointers to classes derived from a common base class. The catch is, I wish to do it in a single line, without declarating the vector first and then passing it by reference to the function. I came up with the following solution, which uses a wrapper class to encapsulate each vector element and pass the respective pointer to the vector constructor through an implicit conversion operator.

I am unsure of the consequences of such approach, specifically if, at any point, the classes are being copied multiple times during the vector construction and/or the function call. Could you guys give me a feedback or even a better solution to the problem? Thanks.

class Base 
{
   public:
};

class Derived1 : public Base 
{
   public:
   Derived1(int number){}
};

class Derived2 : public Base 
{
   public:
   Derived2(const char* string){}
};

template <typename T> class Wrapper
{
   private:
   T element;
   public:
   Wrapper<T>(int number):element(number){}
   Wrapper<T>(const char* string):element(string){}

   operator T* (){return &element;}
};

void Foo(const std::vector<Base*>& list)
{
   //DO SOMETHING
}

int main()
{
   Foo(std::vector<Base*>({Wrapper<Derived1>(1), Wrapper<Derived2>("name")}));
   return 0;
}
6
  • 3
    "do it in a single line, without declarating the vector first" -- why? What is the actual problem that you're trying to solve? Commented Apr 27, 2024 at 12:35
  • 3
    Unrelated, but you should be able to do it without explicitly creating a std::vector. It would work if you just use the brace-enclosed list directly to Foo. Commented Apr 27, 2024 at 12:36
  • There is no concrete problem to solve. I just want to make the interface as easy as possible to the cllient, since I will have to call function Foo multiple times. Commented Apr 27, 2024 at 12:36
  • Wrapper<T>(int number) is invalid in c++20. Change it to Wrapper(int number) See Is having a declaration Stack<T>(); for the default ctor valid inside a class template. It was a DR so also invalid in c++17. Commented Apr 27, 2024 at 12:48
  • Side note: The call to Foo in function main is missing a close paren. Commented Apr 27, 2024 at 12:52

3 Answers 3

2

Yes, that's a reasonable way to accomplish it (although the goal might not be worth accomplishing in the first place).

As "Some programmer dude" commented, since Foo is declared to take std::vector<Base*>, it should be fine to just pass the braced elements of that vector without repeating the type std::vector<Base*> on the caller's side. (But if Foo has other overloads, or is a template, then that won't necessarily work.) Also, main returns 0 by default. Thus:

int main() {
   Foo({Wrapper<Derived1>(1), Wrapper<Derived2>("name")});
}

Your Wrapper can perfectly forward its arguments to T's constructor; look at std::make_unique<Derived1>(1) to see how that's done. Also, personally I'd make its API a function with a verb-name, not a class with a noun-name. Godbolt:

template<class T, class... Args>
auto wrap(Args&&... args) {
  struct Wrapper {
    T elt_;
    operator T*() { return &elt_; }
  };
  return Wrapper{ T(std::forward<Args>(args)...) };
}

int main() {
    Foo({wrap<Derived1>(1), wrap<Derived2>("abc")});
}

I observed (by accident :)) that if you forget to make Derived1 derive from Base, the error messages will be crazy cryptic — because you're asking to create a vector from a braced-initializer-list each of whose elements has an implicit conversion to something else. That's a lot of layers of "implicit-ness."


A potentially simpler, but less efficient, approach would be to do something like this (Godbolt), making a whole new vector containing the addresses of elements stored elsewhere:

template<class... Ptrs>
std::vector<Base*> vector_of_ptrs(Ptrs&&... args) {
  return {args.get()...};
}

int main() {
    Foo(vector_of_ptrs(
      std::make_unique<Derived1>(1),
      std::make_unique<Derived2>("abc")
    ));
}

Or for slightly better error messages, insert the elements of the vector by folding over push_back:

template<class... Ptrs>
std::vector<Base*> vector_of_ptrs(Ptrs&&... args) {
  std::vector<Base*> r;
  (r.push_back(args.get()) , ...);
  return r;
}

Oh, and I almost forgot to mention: The idea of having a getter member function that returns a pointer to (part of) a materialized temporary object probably feels a little sketchy, and that's a good thing; but people definitely do that sometimes. See for example the .addr() getter in this blog post.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, @Quuxplusone. Indeed, the fact that my program requires such interface is a serious indicator of poor design. I'll try to think of something else, but for now, I will apply your suggestions to my code and stick with it.
2

Rather than having the wrapper forward arguments to the constructor (adding unnecessary moves in some cases even if done using perfect forwarding; and just not looking good to my taste), I'd do this:

template <typename T>
[[nodiscard]] T &unmove(T &&value)
{
    return static_cast<T &>(value);
}
Foo(std::vector<Base *>{&unmove(Derived1(1)), &unmove(Derived2("name"))});

Such unmove() function lets you take addresses of temporaries, and is useful in some other cases too (e.g. the last usecase I remember is passing rvalues to std::make_format_args()).

Comments

0

I wish to do it in a single line, without declarating the vector first

std::vector has an initializer list constructor so you can already omit the std::vector and just provide the braced init list as shown below:

//--v------------------------------------------>no need to explicitly specify std::vector<Base*>
Foo({Wrapper<Derived1>(1), Wrapper<Derived2>("name")});

Note that both are equivalent in term of functionality but the modified version with only braced init list requires less keystrokes.

Sitenote

Additionally note that Wrapper<T>(int number) is invalid in c++20(as DR) and so also invalid in c++17. The correct way to write it is Wrapper(int number).

Demo

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.