The following code
#include <string>
#include <map>
#include <cassert>
struct AV {
explicit AV(std::string const&) {}
};
#if 1
static void check_cache_item(
std::map<std::string, std::string> const& items) // FIXME remove
{
assert(!items.empty());
}
#endif
static void check_cache_item(
std::map<std::string, AV> const& items)
{
assert(!items.empty());
}
int main()
{
check_cache_item({ { "id", "0" }, { "pk", "#0" } });
check_cache_item({ { "id", "0" }, { "pk", "#1" } });
check_cache_item({ { "id", AV{"0"} }, { "pk", AV{"#1"} } });
}
is accepted by g++ 4.8.4, g++ 5.3.0, clang++ 3.9.0; but g++ 6.1.0 gives an error:
cci.cc: In function ‘int main()’:
cci.cc:25:55: error: call of overloaded ‘check_cache_item(<brace-enclosed initializer list>)’ is ambiguous
check_cache_item({ { "id", "0" }, { "pk", "#0" } });
^
cci.cc:10:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)
static void check_cache_item(
^~~~~~~~~~~~~~~~
cci.cc:16:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, AV>&)
static void check_cache_item(
^~~~~~~~~~~~~~~~
cci.cc:26:55: error: call of overloaded ‘check_cache_item(<brace-enclosed initializer list>)’ is ambiguous
check_cache_item({ { "id", "0" }, { "pk", "#1" } });
^
cci.cc:10:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)
static void check_cache_item(
^~~~~~~~~~~~~~~~
cci.cc:16:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, AV>&)
static void check_cache_item(
^~~~~~~~~~~~~~~~
cci.cc: At global scope:
cci.cc:10:17: warning: ‘void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)’ defined but not used [-Wunused-function]
static void check_cache_item(
^~~~~~~~~~~~~~~~
If I #ifdef out the first constructor, then every compiler throws an error (rightly, because the AV constructor is explicit).
Is this a regression in G++ 6.1.0 ?
This is a surprising and somewhat unfortunate aspect of the Standard (I would go so far as to call it a defect); it is the result of a collision between the overload resolution rules for copy-list-initialization ([over.match.list] as confirmed in CWG 1228), and the element-forwarding constructor of pair
(as per n4387).
gcc (>= 6.1.0) is correct to reject your program; clang is incorrect to accept it. Earlier versions of gcc accept your program because they had not yet implemented n4387; clang accepts your program because it excludes explicit constructors from consideration for overload resolution for copy-list-initialization, which violates [over.match.list] according to the Standard (Calling an explicit constructor with a braced-init list: ambiguous or not?)
If we peel away the extraneous aspects of your program it comes down to a simple question of overload resolution:
struct A { explicit A(int, int); };
struct B { B(int, int); };
void f(A);
void f(B);
int main() { f({0, 0}); }
Here A
is standing in for pair<std::string const, AV>
and B
is standing in for pair<string const, string>
. The constructor of A
is explicit because pace n4387 it involves the explicit constructor of AV
; but per CWG 1228 the rules for copy-list-initialization:
[...] include all constructors but state that the program is ill-formed if an explicit constructor is selected by overload resolution. [...]
[over.match.list]:
[...] In copy-list-initialization, if an
explicit
constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations ([over.match.ctor], [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ]
Thus your program is correctly considered (under the Standard as it currently is) to be ambiguous.
Further reading: What could go wrong if copy-list-initialization allowed explicit constructors?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With