C++:显式实例化模板时的隐式转换

C++: Implicit conversion when templates are explicitly instantiated

提问人:blipblop 提问时间:11/15/2023 最后编辑:blipblop 更新时间:11/15/2023 访问量:107

问:

众所周知,当我们想在头文件中拥有模板化类/函数的声明,并在源 cpp 文件中定义它们时,必须在所述 cpp 文件的末尾添加显式实例化 - 但限制是,我们的模板仅适用于显式定义的情况。

例如,假设我们有一个以下标头,其中包含一个模板化类,该类也具有模板化成员函数:

// header classA.h

template<class T>
class ClassA
{
    /* template<class U> */
    /* friend class ClassA; */
public:
    template<typename U>
    void foo(U val);
};

extern template class ClassA<double>;
extern template void ClassA<double>::foo(double);

在这里,告诉编译器,每当包含此标头时,编译器都不应尝试在此处实例化 ClassA 或其成员函数 - 定义将出现在其他地方。extern

在我们的例子中,它们来自以下 cpp 源文件:

// header classA.cpp

#include <stdio.h>
#include "classA.h"


template<class T>
template<typename U>
void ClassA<T>::foo(U val)
{
    // other code that uses val goes here
    printf("foo\n");
}

template class ClassA<double>;
template void ClassA<double>::foo(double);

我们用这个 main.cpp 测试代码:

#include <stdio.h>
#include "classA.h"

void test(double x) { printf("test\n"); }

int main()
{
    ClassA<double> a;
    float x = 1.0; // this being a float throws a linker error; it must be double
    a.foo(x);
    test(x);
    return 0;
}

上面的代码按原样不编译,因为 是 .链接器(使用 G++ 12.3.0)抛出链接错误:xfloatld

/usr/bin/ld: /tmp/ccVYWPa2.o: in function `main':
main.cpp:(.text.startup+0x26): undefined reference to `void
ClassA<double>::foo<float>(float)' collect2: error: ld returned 1 exit
status bash: ./a.out: No such file or directory``

如果我们更改为 ,则程序将按预期编译。也就是说,当 是 时,从 float 到 double 的隐式转换不会启动。我不明白的是为什么。当然,一般来说,编译器不能明确地决定模板化参数的类型。但是,由于给出了一个显式实例化 - 在这个简单的示例中只有一个 - 我希望编译器在传递给时将浮点数隐式转换为双精度 - 因为没有其他选项被显式实例化以产生歧义。xdoublexfloatfoo

也就是说,当显式实例化更多情况时,我当然可以理解歧义,例如:

template class ClassA<double>;
template void ClassA<double>::foo(double);
template class ClassA<double>;
template void ClassA<double>::foo(long double);

所以,我的问题是双重的:

  1. 如果没有显式实例化的模棱两可的情况,为什么隐式转换不会在前夕发生?

  2. 在这样的例子中,有没有办法至少在 T = U强制执行隐式转换(即可能基于 ,类似于对类似但不同情况的回答)?T

编辑:显然,我需要两者并分开。否则,我可以删除函数模板并使用 .TUTU

C++ 模板 隐式转换 类型特征 显式实例化

评论

0赞 Weijun Zhou 11/15/2023
它不是.此外,实例化在类型推导中根本不起作用。explicit instantiationexplicit initialization
0赞 blipblop 11/15/2023
@JaMiT这是我的错别字/编辑错误。 刚刚更正。忘记 int,尝试使用 and,将显示错误。但它会用 正确编译。ClassA<double>float xdouble x

答:

0赞 Ben 11/15/2023 #1
  1. 它没有链接,因为匹配,然后它正在寻找一个不存在的定义。floatU
  2. 作为函数参数,因此函数不会被模板化(只是它所属的类)。然后会期望一个 ,这将导致进行隐式转换。然后它不会链接,因为你只提供了一个版本。Ta.foointa.foo(x)double

评论

0赞 blipblop 11/15/2023
谢谢你的回答。但是我必须单独拥有,否则我当然可以简单地删除函数模板TU
1赞 Ben 11/15/2023
然后根据你想要什么,添加一个 and do .或者你可以花哨并提供超载来为你做这个对话。extern template void ClassA<int>::foo(double);a.foo<double>(x)
0赞 blipblop 11/15/2023
但这些也与隐式转换无关,不是吗?另外,关于第 1 点,当然,匹配并且没有实例化。但同样,只有一个实例化:for double,浮点数可以隐式转换为哪个实例。没有歧义floatUfloat
0赞 Ben 11/15/2023
没有歧义:将找到然后无法链接。就是这么简单。externes 不影响编译器,只影响链接器。如果你想增加复杂性,你可以这样做。a.foo(x)a.foo<float>(x)template <std::convertible_to<T> U> void foo(U x) { this->foo<T>(x); }
0赞 Weijun Zhou 11/15/2023 #2
  1. 这是因为被推导为,实例化在类型推导中根本没有作用。在类型不完全匹配的情况下提供显式实例化无法阻止编译器在类型完全匹配的情况下生成隐式实例化。它根本不是关于重载解决方案,因此关于歧义的论点在这里根本不适用。Ufloat
  2. 您可以使用 SFINAE 技术创建简单的转发函数(专用化)。我在这里使用概念,但你可以使用其他适合你的 c++ 版本的技术。对于这些转发函数,您需要提供类内定义。类似的东西
#include <concepts>
#include <utility>

template<class T>
class ClassA
{
    /* template<class U> */
    /* friend class ClassA; */
public:
    template<typename U>
    void foo(U val);
    
    template<typename U>
    requires (std::assignable_from<T&, U> && !std::same_as<std::remove_cvref_t<U>, T>)
    void foo(U&& val){
        return foo<T>(std::forward<U>(val));
    }
};

extern template class ClassA<double>;
extern template void ClassA<double>::foo(double);