作为参数传递给函数模板时,如何隐式转换类模板对象?

时间:2018-11-21 17:28:12

标签: c++ class templates casting implicit-conversion

考虑以下小代码段:

template <typename T>
class A{
public:
    A() { };
    ~A() { };
    // ...
    template <typename outT = T> operator std::vector<outT>(){ /* implicit/explicit cast to std::vector */ }    
};

template <typename T = float>
void myFunc(const std::vector<T>& _arg){
    printf("myFunc\n");
}

int main(int argc, char const *argv[]) { 
    A<int> foo;
    myFunc(foo);
}

尝试编译时,出现错误

template argument deduction/substitution failed: [...]  ‘A<int>’ is not derived from ‘const std::vector<T>’

另一方面,如果myFunc不是模板,它将编译并正常运行。我假设该错误是由于以下事实造成的:由于myFunc接受多种向量类型作为参数,因此编译器不知道将foo转换为哪种类型。但是,在这种情况下,是否不应将默认模板值用作后备?有没有其他方法可以将类A的对象传递给myFunc

3 个答案:

答案 0 :(得分:1)

尝试推导模板参数时不考虑隐式转换。由于A<int>无法匹配const std::vector<T>,因此myFunc模板不是可行的候选者。即使您事先explicitly instantiate(即使转换运算符为not templated),也是如此。

对于重载解决方案,考虑了隐式转换 ,这就是非模板版本起作用的原因。模板版本永远不会参与,因为它不是可行的候选人。

答案 1 :(得分:1)

问题在于模板参数推导不考虑隐式转换,因此,当您编写myFunc(foo)时,编译器无法确定T的类型。这实际上是故事的结尾。

有关C ++参考,请参见Template argument deduction

Walter E. Brown在CppCon2018上做了精彩的演讲,讨论了功能模板的工作原理(包括为什么要称其为功能模板而不是模板化功能),我强烈建议

在youtube上查看“ C++ Function Templates: How Do They Really Work?”。

答案 2 :(得分:0)

如其他答案所述,模板解析不能考虑隐式转换。解决问题的一种简单方法是重载myFunc并进行显式转换。考虑以下代码:

#include <vector>
#include <iostream>

template <typename T>
class A{
public:
    A() { };
    ~A() { };
    // ...
  template <typename outT = T> operator std::vector<outT>() const { /* implicit/explicit cast to std::vector */
    std::cout << "I am doing a conversion" << std::endl;
    return std::vector<outT>(); }
};

template <typename T = float, typename... Args>
void myFunc(const std::vector<T, Args...>& _arg){
  std::cout << "myFunc" << std::endl;
}

template <typename T>
void myFunc(const A<T>& _arg)
{
  std::cout << "I am the second variant of myFunc" << std::endl;
  myFunc(static_cast<const std::vector<T>>(_arg));
}

int main(int argc, char const *argv[]) { 
    A<int> foo;
    std::cout << "-- First call of myFunc" << std::endl;
    myFunc(foo);
    std::cout << "-- Second call of myFunc" << std::endl;
    std::vector<double> x = foo;
    myFunc(x);

    return 0;
}

程序输出为:

-- First call of myFunc
I am the second variant of myFunc
I am doing a conversion
myFunc
-- Second call of myFunc
I am doing a conversion
myFunc

当您将foo传递给myFunc时,编译器将使用myFunc的第二个重载,该重载通过显式强制转换调用转换运算符。在第二种情况下,我们直接将foo分配给std::vector,因此在将生成的std::vector传递给myFunc的第一个重载之前进行转换。请注意:

  • 如果您要保持const的重载不变性,则需要一个myFunc转换运算符。

  • typename... Args的第一个重载中,您需要一个附加的模板参数包myFunc来捕获std::vector的每个特殊化(例如,使用自定义分配器)。

另一个选择是将A声明为从std::vector派生的类。类似于以下代码:

#include <vector>
#include <iostream>

template <typename T>
class A : public std::vector<T> {
public:
  A() : std::vector<T>(/* Some parameters to initialize the base class */) { }
};

template <typename T = float, typename... Args>
void myFunc(const std::vector<T, Args...>& _arg){
  std::cout << "myFunc" << std::endl;
}

int main(int argc, char const *argv[]) { 
    A<int> foo;
    myFunc(foo);

    return 0;
}

在这种情况下,您不需要进行转换,而是通过切片调用myFunc。这种方法的缺点是您需要在构造函数中进行到std::vector的转换,并且每次修改A时,都必须保持基类std::vector的数据为最新。