variadic template method to create object

Viewed 175

I have a variadic template method inside a template class (of type T_) looking like this

template < typename T_ >
class MyContainer {
  public:
  
  ...
  template <typename ...A>
  ulong add (A &&...args) 
  {
    T_ t{args...};
    // other stuff ...
    vec.push_back(t);
    // returning an ulong 
  }
}

So basically I'm trying to make this class adapt to whatever type T_ but since I can't know in advance which types its constructor requires, I use a variadic. I took inspiration from the emplace_back method from the stl library.

Nevertheless, I get a warning of narrowing conversion with integer types if I try to do something like this

MyContainer<SomeClassRequiringAnUlong> mc;
mc.add(2);
warning: narrowing conversion of ‘args#0’ from ‘int’ to ‘long unsigned int’ [-Wnarrowing]

So I was wondering if I can do anything about it. Is there any way to tell the method which parameters' type it is supposed to take according to the template parameter T_ (which is known when the object is created) ?

3 Answers

Is there any way to tell the method which parameters' type it is supposed to take according to the template parameter T_ (which is known when the object is created)?

In your case, you should use direct initialization(()) instead of list initialization({}) (to avoid unnecessary narrowing checks).

Consider the case where T is a vector<int>:

MyContainer<std::vector<int>> mc;
mc.add(3, 0);

What do you expect mc.add(3, 0) to do? In your add() function, T_ t{args...} will invoke vector<int>{3,0} and create a vector of size 2, which is obviously wrong. You should use T_ t(args...) to call the overload of vector(size_type count, const T& value) to construct a vector of size 3, just like emplace_back() does.

It is worth noting that due to P0960R3, T_ t(std::forward<Args>(args)...) can also perform aggregate initialization if T_ is aggregate.

In general, no. The rules of C++ explicitly allow implicit conversions to take place. The fact that the authors of C++ made some of those conversions potentially unsafe is another matter.

You could add std::is_constructible<T,A&&...> static_assert or SFINAE to the code to make the compiler errors less ugly if the user inputs wrong arguments, but it won't solve implicit conversions.

From design perspective, the code should not care about this, the purpose of emplace_XXX is to allow exactly the calls that are allowed for T{args...}.

Note: You most likely want to forward the arguments like T element{std::forward<A>(args)...}; and also move the element into the vector vec.push_back(std::move(t));.

That said, the code

T_ t{args...};
//...
vec.push_back(t);

is the exact opposite what emplace functions do, their purpose is to create the element in-place at its final destination. Not to copy or move it there.

You are looking in the wrong direction. It is not the template, but the user code that needs fix. To see the issue, first understand that 2 is of type signed int. So you are trying to construct an object with a signed int, while the constructor expects long unsigned int instead. A minimal repro of the issue can then be written as below.

class SomeClassRequiringAnUlong {
public:
    SomeClassRequiringAnUlong(unsigned long) {}
};

int main() {
  int v = 2;
  SomeClassRequiringAnUlong obj{v};
}

The warning simply states that this narrowing conversion from signed int to long unsigned int is potentially risky and may catch you off guard. E.g.,

int v = -1;
SomeClassRequiringAnUlong obj{v};

still compiles and runs, but the result may not be what you want. Now you see that the issue lies in the user code. I see two ways to fix. 1) Use the type the constructor expects from the very beginning. In your case, that would be changing mc.add(2) to mc.add(2ul). 2ul is of type long unsigned int. 2) Make the type conversion explicit to inform the compiler that the narrowing conversion is by design and it is fine. That would be changing mc.add(2) to mc.add(static_cast<long unsigned int>(2)).

Note that there are issues in your template (though not quite related to the warning) as other answers have noted. But they are irrelevant to the specific question you asked. So I do not further elaborate on them.

Related