C++ 是否使用一个参数进行模板参数推断?

C++ 是否使用一个参数进行模板参数推断?,c++,templates,template-argument-deduction,C++,Templates,Template Argument Deduction,假设我有一个模板函数,assign()。它接受一个指针和一个值,并将该值分配给指针的目标: template <typename T> void assign(T *a, T b) { *a = b; } int main() { double i; assign(&i, 2); } 有没有一种方法可以声明assign(),使第二个参数不参与模板参数的推导?只是将值2推导为类型int,该类型与&I推导的模板参数不匹配。您需要将该值用作双精度: assig

假设我有一个模板函数,
assign()
。它接受一个指针和一个值,并将该值分配给指针的目标:

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
    double i;
    assign(&i, 2);
}


有没有一种方法可以声明
assign()
,使第二个参数不参与模板参数的推导?

只是将值
2
推导为类型
int
,该类型与
&I
推导的模板参数不匹配。您需要将该值用作双精度:

assign(&i, 2.0);
为什么不使用两种独立的参数类型,一种用于源,另一种用于目标

template <typename D, typename S> void assign(D *a, S b) { *a = b; }

int main(int argc, char* argv[])
{
    double i;
    assign(&i, 2);
    return 0;
}
template void assign(D*a,sb){*a=b;}
int main(int argc,char*argv[])
{
双i;
分配(&i,2);
返回0;
}

如果赋值不可能,模板实例化将无法编译。

问题是编译器正在从第一个和第二个参数推断冲突信息。从第一个参数推断出
T
double
i
double)
;从第二个,它推断
T
int
2
的类型为
int

这里有两种主要的可能性:

  • 始终明确说明参数的类型:

    assign(&i, 2.0);
    //         ^^^^
    
  • 或者让函数模板接受两个模板参数:

    template <typename T, typename U> 
    void assign(T *a, U b) { *a = b; }
    
    U
    不能转换为
    T
    时,如果您不需要将函数从重载集中排除,您可能希望在
    assign()
    中有一个静态断言,以产生更好的编译错误:

    #include <type_traits>
    
    template<typename T, typename U>
    void assign(T *a, U b)
    {
        static_assert(std::is_convertible<T, U>::value,
            "Error: Source type not convertible to destination type.");
    
        *a = b;
    }
    
    #包括
    模板
    无效分配(T*a,U-b)
    {
    静态断言(std::是可转换的::值,
    “错误:源类型无法转换为目标类型。”);
    *a=b;
    }
    

使用两个类型参数可能是最好的选择,但如果您真的只想从第一个参数执行推断,只需将第二个参数设置为不可推断:

template<typename T>
void assign( T* a, typename std::identity<T>::type b );
模板
无效分配(T*a,类型名称std::identity::类型b);
  • 演示:


这个答案的早期版本建议使用C++11中引入的模板别名功能。但模板别名仍然是一个可推断的上下文。
std::identity
std::remove_reference
阻止推断的主要原因是模板类可以被专门化,因此即使您有一个模板类型参数的typedef,另一个专门化也可能有相同类型的typedef。由于可能存在歧义,因此无法进行推断。但是模板别名排除了专门化,因此仍然会发生推断。

或者,您可以使用
decltype
将第二个参数类型转换为第一个参数的类型

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
    double i;
    assign(&i, (decltype(i))2);
}
template void assign(T*a,tb){*a=b;}
int main(){
双i;
分配(&i,(decltype(i))2);
}

我的尝试如下所示:

template<typename T, typename U>
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect
assign( T* dest, U&& src ) {
  *dest = std::forward<U>(src);
}

上述操作将保存1
move
。如果您有一个移动成本很高的类,或者一个仅复制且复制成本很高的类,这可以节省大量的成本。

显然
std::identity
不再存在()

但调用函数时,可以在参数类型列表中指定参数类型:

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
  double i;
  assign<double>(&i, 2);
}
template void assign(T*a,tb){*a=b;}
int main(){
双i;
分配(&i,2);
}
通过这种方式,编译器将整数输入参数转换为double以匹配函数模板,而不进行参数推导


C++20具有
std::type_identity
,可用于建立非推断上下文:

#包括
模板
无效分配(T*a,std::type_identity_T b){
*a=b;
}
int main(){
双i;
分配(&i,2);
}

static\u assert
在函数中,因为在不可转换的情况下没有另一个重载。@Praetorian:或者,是的。我没有考虑它,因为函数很小,错误消息无论如何都是可以理解的,但你是对的,这是一个选项,担心后期编辑-这是一个相当合法的答案。“至少,我投了赞成票。@Sidnicious:没问题:)我知道。这是可行的,但它是脆弱和丑陋的-如果
i
是一个
无符号长的
,那么我的第二个参数必须是
2ul
,如果它是一个浮点,它必须是
(float)2
,等等。在真正的代码中,这需要一些争论,而且速度很快。@Sidnicious那么我想你可能会同意安迪的答案。奇怪的是,源代码使用
D
,源代码使用
s
destination@Ben:确实:)刚刚交换了它们。如果要这样做,为什么不完美地转发
S
?+1。考虑到问题的编辑(我错过了),这似乎是正确的答案。这是一个非常酷的答案,但对我来说并不适用。我从编译器中得到了相同的“冲突类型”错误:ideone.com/gijbj1看起来像
std::remove\u reference
也是等效的(如果可读性稍差)。@Sidnicious:my bad,看起来模板别名仍然是可推断的:(没有
std::identity
,是吗?因此,上面的一个问题是效率低下。假设
T
std::vector
。参数
b
按值取值,然后复制(不移动)到
a
。一个小的改进可能是将
assign
的实现更改为
*a=std::move>(b)
,对于基元类型来说,这不需要花费什么,对于复杂类型可以节省很多。一个很大的改进是完善forward
b
@Yakk完全同意-我只是把它作为一个函数的例子来写的,这个函数需要指向同一类型的指针和值。实际上,它只需要基元,比这个家伙更有用:).是的,但你必须记住每次打电话都要这么做。事情很快变得很糟糕-我发这个问题是因为我是w
template<typename T, typename U>
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect
assign( T* dest, U&& src ) {
  *dest = std::forward<U>(src);
}
template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
  *dest = std::move(src);
}
template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
  double i;
  assign<double>(&i, 2);
}