I have this code, relying on a fold expression calling a very generic comparison operator:
#include <type_traits>
#include <vector>
#include <list>
template <typename Iter_a, typename Iter_b>
auto operator<(Iter_a, Iter_b) {
// original code makes more sense, I assure you
if constexpr ( std::is_same_v<Iter_a, Iter_b> ) {
return Iter_a();
}
else return Iter_b();
}
template <typename... Iterators>
using weakest_iterator = std::decay_t<decltype( (Iterators() < ...) )>;
int main() {
using lit = std::list<int>::iterator;
using vit = std::vector<int>::iterator;
using wi = weakest_iterator<lit, vit, float*>;
}
I have two problems with this code:
The first is that g++ compiles it and clang++ (-std=gnu++2a) refuses to do so:
prog.cc:14:64: error: call to function 'operator<' that is neither visible in the template definition nor found by argument-dependent lookup
using weakest_iterator = std::decay_t<decltype( (Iterators() < ...) )>;
^
prog.cc:19:16: note: in instantiation of template type alias 'weakest_iterator' requested here
using wi = weakest_iterator<lit, vit, float*>;
^
prog.cc:6:6: note: 'operator<' should be declared prior to the call site
auto operator<(Iter_a, Iter_b) {
Do you have any idea why? I find particularly troubling that clang requests a definition prior to the call site at line 19, that it finds by itself at line 6.
The second is that, if I modify it to invoke my meta-function with pointer template arguments (using wi = weakest_iterator<int*, float*, float*>;, I get another error message, this time only with g++ since clang refuses to compile, that I find difficult to really understand:
main.cpp: In substitution of 'template<class ... Iterators> using weakest_iterator = std::decay_t<decltype ((Iterators() < ...))> [with Iterators = {int*, float*, float*}]':
main.cpp:19:57: required from here
main.cpp:14:75: error: ISO C++ forbids comparison between pointer and integer [-fpermissive]
using weakest_iterator = std::decay_t<decltype( (Iterators() < ...) )>;
It seems that 1) the built-in overload of operator< is called (if there is anything such as this) over my ultra-generic one, and 2) that comparing an int* and a float* is considered the same as comparing a pointer and an integer.
Is it possible to make sure that my own implementation of operator< is chosen by the compiler?
Related
I added the following function template to my project, and a user complained that it wouldn't compile on their system anymore:
template<typename T>
std::size_t removeDuplicates(std::vector<T>& vec)
{
std::unordered_set<T> seen;
auto newEnd = std::remove_if(
vec.begin(), vec.end(), [&seen](const T& value)
{
if (seen.find(value) != std::end(seen))
return true;
seen.insert(value);
return false;
}
);
vec.erase(newEnd, vec.end());
return vec.size();
}
The error message with g++ 9.4 was approximately
error: use of deleted function
'std::unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set()
[with
_Value = std::filesystem::__cxx11::path;
_Hash = std::hash<std::filesystem::__cxx11::path>;
_Pred = std::equal_to<std::filesystem::__cxx11::path>;
_Alloc = std::allocator<std::filesystem::__cxx11::path>]'
12 | std::unordered_set<T> seen;
So the error arose when instantiating the above function template with T = std::filesystem::path.
I investigated a little and found that there was no issue when instantiating it with other types, e.g. fundamental types or std::string, but only with std::filesystem::path.
Using Compiler Explorer, I looked at how different compiler versions treat the code and found that only g++ v.12 can compile the instantiation with std::filesystem::path. Any g++ version below 12 fails with the above error. clang produces a similar error (call to implicitly deleted default constructor), even on the newest version (14). I didn't test other compilers.
The workaround I used was substituting std::unordered_set with std::set. Then it works on g++ v.8 and clang v.7 and above.
So I guess the error is a missing hashing function for std::filesystem::path? Or is it an error on my part?
The std::hash specialization for std::filesystem::path has only recently been added as resolution of LWG issue 3657 into the standard draft. It hasn't been there in the published C++17 and C++20 standards.
There has always been however a function std::filesystem::hash_value from which you can easily create a function object to pass as hash function to std::unordered_set:
struct PathHash {
auto operator()(const std::filesystem::path& p) const noexcept {
return std::filesystem::hash_value(p);
}
};
//...
std::unordered_set<std::filesystem::path, PathHash> seen;
If you are providing that template without any guarantees that it works on types other than those that have a std::hash specialization defined, then I think there is no problem on your part.
However, if you require the type to be hashable, it would be a good idea to let the user override the hash function used the same way std::unordered_set does. The same also applies to the equality functor used.
So I want to use a custom type (here, SWrapper) as the key type for an unordered_multimap. I've defined a hash class, derived from the standard hashing function for strings, and included the hashing class in the type of the multimap. Some code that reproduces the error is displayed below. This compiles on Arch Linux with both g++ and clang++, but on MacOS with clang++, I get errors:
#include <unordered_map>
#include <functional>
#include <string>
class SWrapper {
public:
SWrapper() {
(*this).name = "";
}
SWrapper(std::string name) {
(*this).name = name;
}
bool operator==(SWrapper const& other) {
return (*this).name == other.name;
}
std::string name;
};
class SWrapperHasher {
size_t operator()(SWrapper const& sw) const {
return std::hash<std::string>()(sw.name);
}
};
int main(int argc, char* argv[]) {
auto mm = std::unordered_multimap<SWrapper, int, SWrapperHasher>();
return 0;
}
Running g++ -std=c++11 -Wall -Wpedantic -Wextra hash_map_test.cpp -o hash_map_test on Arch Linux (or clang++) compiles the code with no errors. However, on MacOS, using the same command, I get the following error message:
In file included from hash_map_test.cpp:1:
In file included from /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/unordered_map:408:
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/__hash_table:868:5: error:
static_assert failed due to requirement 'integral_constant<bool, false>::value' "the
specified hash does not meet the Hash requirements"
static_assert(__check_hash_requirements<_Key, _Hash>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/__hash_table:883:1: note: in
instantiation of template class
'std::__1::__enforce_unordered_container_requirements<SWrapper, SWrapperHasher,
std::__1::equal_to<SWrapper> >' requested here
typename __enforce_unordered_container_requirements<_Key, _Hash, _Equal>::type
^
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/unordered_map:1682:26: note: while
substituting explicitly-specified template arguments into function template
'__diagnose_unordered_container_requirements'
static_assert(sizeof(__diagnose_unordered_container_requirements<_Key, _Hash, _Pred>(...
^
hash_map_test.cpp:29:15: note: in instantiation of template class
'std::__1::unordered_multimap<SWrapper, int, SWrapperHasher, std::__1::equal_to<SWrapper>,
std::__1::allocator<std::__1::pair<const SWrapper, int> > >' requested here
auto mm = std::unordered_multimap<SWrapper, int, SWrapperHasher>();
^
1 error generated.
I've tried interpreting the error message, but I'm really at a loss of what to make of it. If anyone has any suggestions as to what's going on here, and how I can work around this on MacOS, it'd be greatly appreciated!
The compiler's complaint unfortunately does not say which requirement was not met. In this case, the problem comes from SWrapperHasher::operator() being private. Marking that public (changing class to struct does this implicitly) makes the unordered map legal.
I have a simple unit-test using Catch 2.11.1:
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <utility>
#include <any>
namespace A::B
{
namespace C
{
struct S
{
};
}
using type = std::pair<C::S, std::any>;
}
inline bool operator==(A::B::type const&, A::B::type const&)
{
return true;
}
TEST_CASE("test", "[test]")
{
auto t1 = std::make_pair(A::B::C::S(), std::any());
auto t2 = std::make_pair(A::B::C::S(), std::any());
REQUIRE(t1 == t2);
}
The above simple programs generates the following errors:
$ g++ -Wall -Wextra -Wpedantic test-single.cpp -std=c++17
In file included from /usr/include/c++/9/bits/stl_algobase.h:64,
from /usr/include/c++/9/bits/char_traits.h:39,
from /usr/include/c++/9/string:40,
from catch.hpp:457,
from test-single.cpp:2:
/usr/include/c++/9/bits/stl_pair.h: In instantiation of ‘constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&) [with _T1 = A::B::C::S; _T2 = std::any]’:
catch.hpp:2289:98: required from ‘bool Catch::compareEqual(const LhsT&, const RhsT&) [with LhsT = std::pair<A::B::C::S, std::any>; RhsT = std::pair<A::B::C::S, std::any>]’
catch.hpp:2318:34: required from ‘const Catch::BinaryExpr<LhsT, const RhsT&> Catch::ExprLhs<LhsT>::operator==(const RhsT&) [with RhsT = std::pair<A::B::C::S, std::any>; LhsT = const std::pair<A::B::C::S, std::any>&]’
test-single.cpp:28:5: required from here
/usr/include/c++/9/bits/stl_pair.h:449:24: error: no match for ‘operator==’ (operand types are ‘const A::B::C::S’ and ‘const A::B::C::S’)
449 | { return __x.first == __y.first && __x.second == __y.second; }
| ~~~~~~~~~~^~~~~~~~~~~~
[And many many more messages after this...]
The crucial part of the error message is this line:
/usr/include/c++/9/bits/stl_pair.h: In instantiation of ‘constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&) [with _T1 = A::B::C::S; _T2 = std::any]’:
From the error message it's clear that it's the standard std::operator== function for std::pair that is being invoked, instead of my overloaded operator== function.
If I don't do the comparison inside the Catch REQUIRE macro, then it works:
auto result = t1 == t2; // Invokes my overloaded comparison operator
REQUIRE(result);
Now is this a problem with Catch, or with my operator function?
NB: I'm building on Debian SID with a recent build of GCC 9.2
$ g++ --version
g++ (Debian 9.2.1-23) 9.2.1 20200110
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Note that even with the parentheses suggested by Lightness, the code you show is exceptionally fragile.
I guess you are originally in ADL-only territory due to dependent name lookup inside the macro (see the last notes of https://en.cppreference.com/w/cpp/language/adl), and your code is quite clearly not ADL-viable. Adding parentheses makes the whole thing just an unqualified lookup, not ADL-only (again, a guess). The non-ADL part of unqualified lookup saves you in this case, but it will fall apart from entirely unrelated code changes.
Consider this code instead of the TEST_CASE, which is what using parentheses presumably boils down to:
namespace test
{
bool foo()
{
auto t1 = std::make_pair(A::B::C::S(), std::any());
auto t2 = std::make_pair(A::B::C::S(), std::any());
return t1 == t2;
}
}
This compiles and works as expected: https://godbolt.org/z/HiuWWy
Now add a completely unrelated operator== between your global operator== and the t1 == t2:
namespace test
{
struct X{};
bool operator==(X, X);
bool foo()
{
auto t1 = std::make_pair(A::B::C::S(), std::any());
auto t2 = std::make_pair(A::B::C::S(), std::any());
return t1 == t2;
}
}
And you're out for the count: https://godbolt.org/z/BUQC9Y
The operator== in the global namespace isn't found because the (non-ADL part of) unqualified name lookup stops in the first enclosing scope that has any operator==. Since that doesn't find anything useful, it falls back to using the inbuilt std::pair comparison operator (found via ADL), which won't work.
Just put operator overloads in the namespaces of the objects they operate on. And by corollary, don't overload operators for facilities from std (or other namespaces you are not allowed to touch).
Adding from comments:
The standard currently also says that the namespaces of template arguments are considered, so putting the operator== in namespace C would work (because the first template argument of std::pair comes from there): https://godbolt.org/z/eV8Joj
However, 1. that doesn't mesh too well with your type alias and 2. there is some movement to make ADL less wild and I've seen discussion to get rid of the "consider namespaces of template parameters". See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0934r0.pdf:
Why on earth would we look into the namespaces of template arguments? Nothing in there could possibly be part of the interface of the type, unless the template arguments were also base classes or something. -- Herb Sutter
I don't know where this paper stands today but I would avoid relying on this kind of ADL in new code.
The magic that expands the operands in order to provide nice diagnostic output can fall over at times.
The workaround is to disable that with some brackets:
REQUIRE((t1 == t2));
This is effectively the same workaround as you have with the variable.
The documentation mentions this problem in the context of more complex expressions. Exactly why the situation is being triggered in your case I am not certain, but notice from the stack trace how your operator== is not actually being invoked, but instead Catch::BinaryExpr::operator== and Catch::compareEqual, which seems not to have access to (or otherwise chooses not to use) your implementation. Either way, the solution is to disable the decomposition machinery as noted above.
Take the following code, which is characterized by
Reliance on ADL for a specific behavior (volume)
Using decltype for return type and relying on SFINAE to discard extra overloads
namespace Nature {
struct Plant {};
double volume(Plant){ return 3.14; }
}
namespace Industrial {
struct Plant {};
double volume(Plant) { return 100; }
}
namespace SoundEffects {
// A workaround for GCC, but why?
////template<class T> void volume();
template<class aSound>
auto mix(aSound& s) -> decltype(volume(s)*0.1)
{
return volume(s)*.1;
}
struct Samples {
Nature::Plant np;
Industrial::Plant ip;
};
inline double mix(const Samples& s) {
return mix(s.np) + mix(s.ip);
}
}
int main()
{
SoundEffects::Samples s;
assert( mix(s) == 100*.1 + 3.14*.1 );
}
The code as presented (without the template<class T> void volume() line), VS 2012 and clang 3.5 compiles successfully, and runtime is as expected. However, GCC 4.7.2 says:
template-function-overload.cpp: In substitution of 'template<class aSound> decltype ((volume(s) * 1.0000000000000001e-1)) SoundEffects::mix(aSound&) [with aSound = SoundEffects::Samples]':
template-function-overload.cpp:46:4: required from here
template-function-overload.cpp:23:9: error: 'volume' was not declared in this scope
template-function-overload.cpp:23:9: note: suggested alternatives:
template-function-overload.cpp:9:11: note: 'Nature::volume'
template-function-overload.cpp:14:11: note: 'Industrial::volume'
With the extra template volume line, all three compile and run fine.
So, there is clearly a compiler defect here. My question is, which compiler is the defective one? And which C++ standard is being violated?
This was a bug which was fixed since GCC 4.8. Here's a simplified version of the code that gives the same error:
template<class T>
auto buzz(T x) -> decltype(foo(x));
void buzz(int);
int main() {
buzz(5); // error: 'foo' was not declared in this scope
}
In mix(s), both overloads of SoundEffects::mix are compiled into the set of candidate overloads via ADL (SoundEffects is an associated namespace of SoundEffects::Sample). The function template overload is evaluated for viability. The error occurs because volume(s) cannot be resolved to a suitable overload either via pure unqualified lookup or ADL for Sample.
The reason this should pass is that when lookup fails a substitution failure should occur (since volume(s) is dependent) and the template should be rejected from overload resolution. This would leave mix(const Sample&) as the only viable overload to select. The fact that there is a hard error is clearly a sign that this GCC version has a faulty SFINAE implementation.
Consider the following code:
#include <vector>
#include <algorithm>
template <typename Input1, typename Input2, typename Output>
void merge(Input1 begin1, Input1 end1, Input2 begin2, Input2 end2, Output out)
{
}
int main()
{
std::vector<int> a = {1, 2};
int b[] = {3, 4};
int c[4];
merge(a.begin(), a.end(), b, b + 2, c);
}
Compiling yields:
$ clang++ -std=c++11 -stdlib=libc++ merge.cpp
merge.cpp:15:5: error: call to 'merge' is ambiguous
merge(a.begin(), a.end(), b, b + 2, c);
^~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/c++/v1/algorithm:4056:1: note:
candidate function [with _InputIterator1 = std::__1::__wrap_iter<int *>,
_InputIterator2 = int *, _OutputIterator = int *]
merge(_InputIterator1 __first1, _InputIterator1 __last1,
^
merge.cpp:5:6: note: candidate function [with Input1 = std::__1::__wrap_iter<int
*>, Input2 = int *, Output = int *]
void merge(Input1 begin1, Input1 end1, Input2 begin2, Input2 end2, Output out)
^
1 error generated.
Compiler version:
$ clang++ --version
Apple LLVM version 5.0 (clang-500.2.78) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
Why is the call to merge ambiguous? It's not sure if I meant ::merge() or std::merge(), though clearly(?) it should be ::merge() since I'm not specifying any using directives. My merge function is in the global namespace, which I thought wouldn't conflict with anything in the std namespace (since that's the main point of namespaces, right?). If I change a to be an int array like the others, it compiles without any ambiguity. Also, adding the colons and calling ::merge() works fine.
So my question is this: Is this a bug in Clang, or do I have a misunderstanding of namespaces? Why does my call to merge() result in ambiguity when the two functions aren't in the same namespace and I haven't made std::merge() visible with any using directives?
The problem is that std::vector<T>::iterator may be a class type (in your case, it is a class type): during overload resolution the compiler finds all visible declarations of a function. To this end, it goes looking in namespaces possibly associated with its arguments (this is called argument dependent look-up). The type std::vector<T>::iterator is defined in namespace std (or a namespace nested within) and, thus, function from namespace std are considered for overload resolutions. Since std::merge() and your merge() both match equally well, there is an ambiguity.
The easiest way to avoid the problem is to use a different name for the function template. Hiding the associated namespace is possible but not easy: associated namespaces are taken from the location where a class or a class template is defined as well as from its base classes and template arguments. Thus, creating a wrapper template for any iterator type wouldn't be sufficient as it still associates the original namespace with the types. You may try to make your function template a better match but given that it is meant to be as generic as the standard algorithm, this isn't quite viable, either.
It's due to argument dependent look-up (http://en.cppreference.com/w/cpp/language/adl) of an iterator from the std namespace.
you could write ::merge to get your function only, but I'd rather just use a different name.