如何解决模板参数推导中的const / non-const冲突

时间:2016-08-20 23:09:30

标签: c++ templates c++11 c++14 type-deduction

我有一个函数模板,它取一个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

我发现我可以在std::remove_const参数的类型上使用std::decayval来解决错误:

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

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

3 个答案:

答案 0 :(得分:5)

一种解决方案是使用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);
}

答案 1 :(得分:3)

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&很简单,但就是这样。

答案 2 :(得分:2)

不是很优雅,但我想你可以在两个不同的模板参数中分开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...
}