Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a function template that takes a std::pair as well as a value of one of the pair's types. I'd like to call this function using an entry from a std::map as the pair argument.

#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);
}

This fails to compile because the type of the map's entry is std::pair<const int, float>, so the type deduction for T1 has conflicting types: const int via the pair argument, and int via the val argument.

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);
                           ^

What's the best way to resolve this conflict? Ideally I'd like T1 to be deduced as int, but it's OK for it to be const int if that's more straightforward to implement.

I've found that I can resolve the error by using either std::remove_const or std::decay on the type of the val parameter:

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

but I don't know which of those is more appropriate, or if there's some other solution that'd be better.

like image 862
Wyzard Avatar asked Aug 20 '16 23:08

Wyzard


People also ask

How do you call a non const function from a const function?

const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

What is type deduction c++?

Type inference or deduction refers to the automatic detection of the data type of an expression in a programming language. It is a feature present in some strongly statically typed languages. In C++, the auto keyword(added in C++ 11) is used for automatic type deduction.


2 Answers

One solution is to use std::add_const instead of using the keyword const directly.

The roundtrip via a template prevents type deduction via this parameter type:

#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);
}
like image 173
Cheers and hth. - Alf Avatar answered Sep 26 '22 15:09

Cheers and hth. - Alf


template<class T>struct identity{using type=T;};
template<class T>using no_deduce=typename identity<T>::type;

Wrap the second type in no_deduce to block deduction.

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...
}

This both works and is clear why you are doing it.

Now, it might be worth considering what you want to do if T1 is a reference type, and what const& does in that case. Imagine T1 is int& then int& const& becomes just an int&.

This may not be what you want.

Maybe what you want, what you really really want, is:

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...
}

If you want a const& you gotta forget your &, if you wanna prevent val from being modified you better make sure it is const. Now, don't go wasting your precious const&, remove_reference_t and it'll be just fine.

If you wanna deal with volatile, you gotta get with remove_volatile_t. Tie them together forever in a template<class T>using clean_t=std::remove_cv_t<remove_reference_t<T>>. If you wanna const& you have got to

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...
}

Simply const& is to easy, but that's the way it is.

like image 21
Yakk - Adam Nevraumont Avatar answered Sep 24 '22 15:09

Yakk - Adam Nevraumont