I am writing a factory. Both "interface" and the "implementation" are defined by template classes.
#include <memory>
template<class I, class ...Args>
struct IFactory
{
virtual std::unique_ptr<I> Create(Args... args) = 0;
};
template<class I, class C, class ...Args>
struct Factory : IFactory<I, Args...>
{
std::unique_ptr<I> Create(Args... args) override
{
return std::make_unique<C>(std::forward<Args>(args)...); // args are no forwarding references
}
};
The code violates the sonar source rule RSPEC-5417, which states:
std::forward has a single use-case: to cast a templated function parameter of type forwarding reference (
T&&
) to the value category (lvalue or rvalue) the caller used to pass it. [...] An error [...] has less dire consequences [than usingstd::move
on a forwarding reference], and might even work as intended if the right template argument is used, but the code would be clumsy and not clearly express the intent.
[Emphasis by me]
I wonder,
- what are the less dire consequences if wrong template arguments used?
- what are the wrong template arguments?
- how to ensure the right template parameters are used?
- how to write the code less clumsy and express intent more clearly?
I considered to use static_cast<Args&&>()
directly, but that would make the code less readable in my opinion and i think it would only re-implement std::forward
.
Example usage of Factory<...>
shows that Factory::Create()
generates one additional move construction (for the ctor argument T1 a
, in the example below):
#include <string>
#include <iostream>
void P(std::string msg){std::cout << msg << std::endl;} // Debug print
// Some types used as ctor arguments types of the class for which we want to create objects.
struct T1{T1()=default; T1(const T1&){P("T1&");} T1(T1&&){P("T1&&");}}; // Move- and copyable
struct T2{T2()=default; T2(const T2&){P("T2&");} T2(T2&&)=delete; }; // Copyable
struct T3{T3()=default; T3(const T3&)=delete; T3(T3&&){P("T3&&");}}; // Moveable
struct T4{T4()=default; T4(const T4&)=delete; T4(T4&&)=delete; }; // None of move/copy
// Interface of the class
struct IType
{ /*Some pure virtual functions.*/
};
struct Type : IType
{
T1 t1;
T2 t2;
T3 t3;
T4& t4;
Type(T1 a, const T2& b, T3&& c, T4& d)
:t1(a), t2(b), t3(std::move(c)), t4(d) {}
};
void F(const IFactory<IType, T1, const T2&, T3&&, T4&>& factory)
{
T1 t1;
T2 t2;
T3 t3a, t3b;
T4 t4;
std::cout << "Ctor:\n";
Type obj1(t1, t2, std::move(t3a), t4);
std::cout << "Factory:\n";
auto ptri1 = factory.Create(t1, t2, std::move(t3b), t4);
}
int main()
{
Factory<IType, Type, T1, const T2&, T3&&, T4&> factory1;
F(factory1);
return 0;
}
Output:
Ctor:
T1&
T1&
T2&
T3&&
Factory:
T1&
T1&& <- additional move for the not optimal ctor argument
T1&
T2&
T3&&