如何解决模板参数推导中的常量/非常量冲突

How to resolve const/non-const conflict in template argument deduction

本文关键字:常量 冲突 非常 参数 何解决 解决      更新时间:2023-10-16

我有一个函数模板,它接受一个std::pair以及这对类型之一的值。我想使用std::map中的一个条目作为对参数来调用此函数。

#include <map>
#include <utility>
template <typename T1, typename T2>
void do_stuff(std::pair<T1, T2> const &pair, T1 const &val) {
  // Imagine that this does something important...
}
int main() {
  std::map<int, float> foo { { 0, 0.0 } };
  do_stuff(*foo.begin(), 0);
}

由于映射项的类型为std::pair<const int, float>,因此无法编译,因此T1的类型推导具有冲突类型:const int通过pair参数,int通过val参数。

test.cc: In function ‘int main()’:
test.cc:12:27: error: no matching function for call to ‘do_stuff(std::pair<const int, float>&, int)’
   do_stuff(*foo.begin(), 0);
                           ^
test.cc:5:6: note: candidate: template<class T1, class T2> void do_stuff(const std::pair<_T1, _T2>&, const T1&)
 void do_stuff(std::pair<T1, T2> const &pair, T1 const &val) {
      ^~~~~~~~
test.cc:5:6: note:   template argument deduction/substitution failed:
test.cc:12:27: note:   deduced conflicting types for parameter ‘const T1’ (‘const int’ and ‘int’)
   do_stuff(*foo.begin(), 0);
                           ^

解决这场冲突的最佳方法是什么?理想情况下,我希望T1被推导为int,但如果实现起来更简单,那么它可以是const int

我发现我可以通过在val参数的类型上使用std::remove_conststd::decay来解决错误:

void do_stuff(std::pair<T1, T2> const &pair, typename std::remove_const<T1>::type const &val) {

但我不知道其中哪一个更合适,或者是否有其他更好的解决方案。

一种解决方案是使用std::add_const,而不是直接使用关键字const

通过模板的往返防止通过以下参数类型进行类型推导:

#include <map>
#include <type_traits>
#include <utility>
template< class T1, class T2 >
void do_stuff( std::pair<T1, T2> const& pair, std::add_const_t<T1>& val )
{
    // Imagine that this does something important...
    (void) pair; (void) val;
}
auto main()
    -> int
{
    std::map<int, float> foo { { 0, 0.0f } };
    do_stuff(*foo.begin(), 0);
}
template<class T>struct identity{using type=T;};
template<class T>using no_deduce=typename identity<T>::type;

no_deduce中包装第二种类型以阻止推导。

template <typename T1, typename T2>
void do_stuff(std::pair<T1, T2> const &pair, no_deduce<T1> const &val) {
  // Imagine that this does something important...
}

这两者都有效,并且很清楚你为什么要这么做。

现在,如果T1是引用类型,那么您想做什么,以及const&在这种情况下会做什么,这可能值得考虑。假设T1int&,那么int& const&就变成了int&

这可能不是你想要的。

也许你想要的,你真正想要的,是:

template <typename T1, typename T2>
void do_stuff(std::pair<T1, T2> const &pair, std::remove_reference_t<T1> const &val) {
  // Imagine that this does something important...
}

如果你想要一个const&,你必须忘记你的&,如果你想防止val被修改,你最好确保它是const。现在,不要浪费你宝贵的const&remove_reference_t,一切都会好起来的。

如果你想处理volatile,你必须处理remove_volatile_t。用template<class T>using clean_t=std::remove_cv_t<remove_reference_t<T>>把它们永远绑在一起。如果你想要const&,你必须去

template <typename T1, typename T2>
void do_stuff(std::pair<T1, T2> const &pair, clean_t<T1> const &val) {
  // Imagine that this does something important...
}

简单地说const&很容易,但事实就是这样。

不是很优雅,但我想您可以在两个不同的模板参数中分离T1

类似的东西

template <typename T1, typename T2, typename T3>
void do_stuff(std::pair<T1, T2> const &pair, T3 const &val) {
  // Imagine that this does something important...
}

您可以通过std::enable_if_t添加一个检查,以在T1T3之间施加相关性;例如

template <typename T1, typename T2, typename T3,
         typename = std::enable_if_t<std::is_same<std::remove_const_t<T1>, std::remove_const_t<T3>>::value>>
void do_stuff(std::pair<T1, T2> const &pair, T3 const & val) {
  // Imagine that this does something important...
}