c++ container inside another container

Viewed 149
#include <bits/stdc++.h>

template <typename T>
struct Row {
    Row() { puts("Row default"); }
    Row(const Row& other) { puts("Row copy"); }
    Row(Row&& other) { puts("Row move"); }
    Row(T x) { puts("Row int"); }
};

int main() {
  Row<Row<int>> x (10); // this works
  Row<Row<int>> x2 = 10; // error
}

Why the code doesn't work? I know that first one performs direct initialization and the second one is copy initialization but still when it comes to container inside a container I doesn't get easy for me.

compiler: clang c++20

Error message:

no viable conversion from 'int' to 'Row<Row >'

Row<Row> x2 = 10

3 Answers

Problem is that copy initialization is less permissive than direct initialization:

> In addition, the implicit conversion in copy-initialization must produce T
  directly from the initializer, while, e.g. direct-initialization expects
  an implicit conversion from the initializer to an argument of T's constructor.

as initialization from int requires 2 conversions (from int to Row<int> and from Row<int> to Row<Row<int>>), direct initialization allows it but copy initialization does not. You can see it in documentation example as well:

struct S { S(std::string) {} }; // implicitly convertible from std::string
S s("abc"); // OK: conversion from const char[4] to std::string
S s = "abc"; // Error: no conversion from const char[4] to S
S s = "abc"s; // OK: conversion from std::string to S

In

Row<Row<int>> x2 = 10;

rather than assignment operator being invoked, the copy-initialisation happens which you mentioned as well. Copy-initialisation calls the copy (or move) constructor of the object, which requires you to pass the object by reference as an argument, which is clearly not possible with the int 10 that you are passing. To make it work, you would have to provide a valid type to copy from:

Row<Row<int>> x2 = Row<int>(10); 

From the copy-constructor reference:

A copy constructor of class T is a non-template constructor whose first parameter is T&‍, const T&‍, volatile T&‍, or const vola

This is what compiler will probably generate(reference cppinsights) for the first call

/* First instantiated from: insights.cpp:12 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Row<int>
{
  inline Row();
  
  inline Row(const Row<int> & other);
  
  inline Row(Row<int> && other);
  
  inline Row(int x)
  {
    puts("Row int");
  }
  
};

#endif


/* First instantiated from: insights.cpp:12 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct Row<Row<int> >
{
  inline Row();
  
  inline Row(const Row<Row<int> > & other);
  
  inline Row(Row<Row<int> > && other);
  
  inline Row(Row<int> x)
  {
    puts("Row int");
  }
  
};

#endif


int main()
{
  Row<Row<int> > x = Row<Row<int> >(Row<int>(10));
}

and your output will be Row int Row int

on Row int from the first template instantiation and one from the second

When you are trying

Row<Row<int>> x2 = 10;

compiler converts 10 to int but it is enable to find such instantiation which can convert directly int to Row<Row<int>>. This will require 2 conversions.

So either you provide one conversion by yourself or use direct initialization.

Related