4

Background

When inserting a std::pair<uint64_t, uint64_t> to a C++ std::map<uint64_t, int>, neither the compiler nor the program complains, even if the passed values are not possible for the data type uint64_t.

In other words, the narrowing conversion of std::pair<uint64_t, uint64_t>(2, -2) isn't working and is defaulting to the map's type std::map<uint64_t, int>

Code

When I compile and execute the following code with g++ -Wall -Wconversion -Wextra -pedantic test/test_wrong_insert.cpp && ./a.out:

#include<map>
#include<iostream>

void print_map(std::map<uint64_t, int> & m){
  std::cout << "The map is now: {";
  for (const auto & n: m){
    std::cout << '(' << n.first << ',' << n.second << ") ";
  }
  std::cout << "}\n";
}

int main(){
  std::map<uint64_t, int> m;

  auto ret = m.insert(std::pair<uint64_t, uint64_t>(2,-2));
  std::cout << "Tried to insert std::pair<uint64_t, uint64_t>(2,-2). ";
  std::cout << "Return: " << ret.second << '\n';
  print_map(m);
}

Result

... this is the output:

Tried to insert std::pair<uint64_t, uint64_t>(2,-2). Return: 1
The map is now: {(2,-2) }

Question

Why does std::pair<uint64_t,uint64_t> x{-1,-2} not produce an error, and how do I make it cause an error?

conchoecia
  • 491
  • 1
  • 8
  • 25
  • Which compiler/version(s) have you tried? – Tim Randall Jan 30 '19 at 19:31
  • I'm using g++ 7.8.0 for this project. Trying clang now... – conchoecia Jan 30 '19 at 19:35
  • I think viable way would be to create/override `std::pair` with object with constructor that accepts `int` as override and rejects negative ones. Probably it is even possible to raise compilation error on negative constants, but not sure if benefit would worse the effort. – Slava Jan 30 '19 at 19:37
  • 2
    i am not sure if I understand the question. Is it basically why `std::pair x{-1,-2};` does not produce an error and how to make it cause an error? – 463035818_is_not_an_ai Jan 30 '19 at 19:41
  • @user463035818, correct - that is what I am trying to ask. – conchoecia Jan 30 '19 at 19:42
  • 1
    I think the problem here is that the ability to use negative values to initialize unsigned integral types is an intentional feature of the language (used in the standard library I believe). For example, `size_t max_value = -1`. And as for conversions, I don't see any narrowing conversions - your values got placed into the map correctly. – Apollys supports Monica Jan 30 '19 at 19:45

1 Answers1

4

Why does std::pair<uint64_t,uint64_t> x{-1,-2} not produce an error?

This is caused by a constructor template that participates in overload resolution when both arguments can be used to construct objects of uint64_t (or whatever type you instantiated std::pair with). It is overload (3) in the list of std::pair constructors, and its template argument deduction leads to a conversion that is taken as intentional (as in auto n = uint64_t{-42}; or static_cast<uint64_t>(-42);) - hence no warning. There's not much you can do about it, as the template parameters of a constructor template can't be given explicitly, as explained here.

[...] how to make it cause an error?

Use std::make_pair and don't rely on template argument deduction:

auto p = std::make_pair<uint64_t, uint64_t>(-42, -42);
//         Be explicit: ^^^^^^^^  ^^^^^^^^

When you compile the above snippet with -Wsign-conversion (important: -Wconversion won't cath it!), it will give you a warning (obviously, add -Werror to treat it as an error).

The issue with std::map::insert is the same, see overload (2) here. This turns any usable given argument into a value_type object, and treats it as any conversion would be the caller's intention. Interestingly, the equivalent member function on a std::set is more restricted. So this is caught:

std::set<uint64_t> s;

s.insert(-42); // complains with -Wsign-conversion
lubgr
  • 37,368
  • 3
  • 66
  • 117