0

Why can't struct screen not initialize the frame struct properly?

What I want is to initialize the screen struct and directly initialize the 2 frame structs as well.

#include <iostream>
#include <sstream>
#include <cstring>

#define ESC "\033"

struct frame {
  public:
    frame(unsigned int w, unsigned int h) :
      m_w(w),
      m_h(h) {}

  private:
    unsigned int m_w, m_h;
};

struct screen {
  public:
    template<typename ... Args>
      screen(Args && ... args0, Args && ... args1) :
        m_f0(std::forward<Args>(args0)...),
        m_f1(std::forward<Args>(args1)...) {}

  private:
    frame m_f0, m_f1;
};

int main() {
  frame f = {16, 16};

  screen s = {f, {16, 16}};

  return 0;
}
Michael
  • 177
  • 2
  • 15
  • *braced-init-list* do not have a type so they cannot be given to a template without specifying the type you want it to represent. – NathanOliver Nov 07 '18 at 13:39
  • @NathanOliver `screen s = {f, f};` does not work either. – Daniel Langr Nov 07 '18 at 13:41
  • @DanielLangr Correct. `{f, f}` is one object so it trys to deduce `Args` with it and can't because `{f, f}` doesn't have a type. – NathanOliver Nov 07 '18 at 13:44
  • @NathanOliver And what about `screen s(f, f)`? My point is that the OP's problem is in my opinion not about _braced-init-list_, as you suggest in the duplicate. It's about multiple parameter packs. – Daniel Langr Nov 07 '18 at 13:46
  • 1
    I recommend looking at how [`std::pair`](https://en.cppreference.com/w/cpp/utility/pair/pair) forwards two sets of arguments to its two data members' constructors. – chris Nov 07 '18 at 13:48
  • @DanielLangr Okay. I've reopened. – NathanOliver Nov 07 '18 at 13:48
  • In a function template, a parameter pack which is not at the end of the parameter declaration list is a non-deduced context. So the type of `args0` cannot be deduced. – n. m. could be an AI Nov 07 '18 at 15:49

1 Answers1

3

{16, 16} has no type. If used in a context to initialize something with a type, it initializes that thing.

Constructor arguments have a type. Deduced template constructor arguments get their type from the argument passed. But {16,16} has no type, so it cannot deduce a type from something that lacks a type.

Your second problem is this:

  template<typename ... Args>
  screen(Args && ... args0, Args && ... args1) :
    m_f0(std::forward<Args>(args0)...),
    m_f1(std::forward<Args>(args1)...) {}

C++ won't deduce Args... for you here. It will only deduce an argument pack if it is the last arguments in the function call, and here Args... is both the last and not the last, so it won't be deduced.

Now you can use make from tuple to some extent:

template<class...Args0, class...Args1>
screen( std::tuple<Args0...> args0, std::tuple<Args1...> args1 ):
  m_f0(std::make_from_tuple<frame>(std::move(args0))),
  m_f1(std::make_from_tuple<frame>(std::move(args1)))
{}

which gets you a touch closer (but not close enough). At the call site you'd do:

screen s = {std::forward_as_tuple(f), std::forward_as_tuple(16, 16)};

and it should now work.

This uses , but make_from_tuple can be implemented as far back as or in C++14.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wouldn't be better to use two template parameter packs, for each function parameter a separate one? In your case, you are actually not forwarding `16, 16` perfectly, since a temporary `frame` is created and forwarded. – Daniel Langr Nov 07 '18 at 14:07
  • 1
    Thi answer is related if you don't have C++17: https://stackoverflow.com/a/41439982/580083. – Daniel Langr Nov 07 '18 at 14:13
  • @DanielLangr Fixed. Note that in C++17, `make_from_tuple` returns a prvalue which isn't a temporary object. So the forwarding is perfect. In C++14/11 it isn't perfect, and making it perfect is a really big hassle. – Yakk - Adam Nevraumont Nov 07 '18 at 14:42