Overload for std::string not detected in template code - c++

I'm writing some template code to determine if a given type can be passed as any argument to any available overload of a function. In the example below I've used the log function, but I've also tried this code on others in the math library, and the results are the same. The idea is to use function overloading and the sizeof operator to distinguish between cases where the type in question can legally be passed to the function in question (log, in this example).
If it worked, we'd have sizeof(overload<type>(NULL)) == sizeof(True) when 'type' can be legally passed to log, and sizeof(overload<type>(NULL)) == sizeof(False) otherwise. This does seems to work for most types, but fails for std::string.
Here's exactly how it fails:
Under normal circumstances we have sizeof(overload<std::string>(NULL)) == sizeof(False), as we should. But, when I declare an overload of log that does take a string, it still doesn't trigger the sizeof(True) branch of the logic. Note that I don't actually want to declare log(std::string) function, I'm just testing this code to make sure that it's able to detect all possible overloads.
At first I thought it just wasn't detecting overloads properly, but when I tried it with a user-defined class ('MyClass' in the example below), it worked fine: it produced sizeof(True) when log(MyClass) was declared, and sizeof(False) otherwise.
#include <iostream>
#include <math.h>
template<int>
struct TakesInt{};
struct True
{
};
struct False
{
// guarantees that sizeof(False) != sizeof(True)
True array[2];
};
// takes anything; fall back if no match can be found
template<typename T>
False overload(...);
// takes a specific type; does not actually call log
template<typename T>
True overload(TakesInt<sizeof(log(T()))>*);
// As a test, this is an overload of log that takes a string.
// I don't actually want to implement this, but it should make the compiler
// think that a string is a valid argument.
double log(std::string);
// a placeholder for user defined class; could really be anything,
// like an arbitrary number class
struct MyClass{};
// declaring log for the arbitrary class above
// note that this is the same as for the log(std::string)
// if one works, the other should
double log(MyClass);
int main()
{
std::cout << sizeof(True) << '\t' << sizeof(False) << std::endl;
std::cout << sizeof(overload<std::string>(NULL)) << std::endl;
std::cout << sizeof(overload<double>(NULL)) << std::endl;
std::cout << sizeof(overload<MyClass >(NULL)) << std::endl;
return 0;
}

Here's the same issue w/o the SFINAE distraction:
#include <iostream>
namespace ns
{
struct string {};
}
void bar(...) { std::cout << "void bar(...)\n"; }
template<class T>
void foo()
{
T x{};
bar(x);
}
void bar(ns::string) { std::cout << "void bar(ns::string)\n"; }
int main()
{
foo<int>();
foo<ns::string>();
}
Output:
void bar(...)
void bar(...)
Lookup of a dependent function name will be performed:
as (pure) unqualified lookup from the point of definition
as (pure) argument-dependent lookup from the point of instantiation
Therefore, the following example differs:
#include <iostream>
namespace ns
{
struct string {};
}
void bar(...) { std::cout << "void bar(...)\n"; }
template<class T>
void foo()
{
T x{};
bar(x);
}
namespace ns
{
void bar(ns::string) { std::cout << "void bar(ns::string)\n"; }
}
int main()
{
foo<int>();
foo<ns::string>();
}
Output:
void bar(...)
void bar(ns::string)
For std::string, the only associated namespace is std. The global namespace is not associated and will not be searched in the OP's code. Therefore, the overload declared after the template definition will not be found.
N.B. Please do not inject overloads into namespace std. This will lead to undefined behaviour as per [namespace.std]/1.

Related

customisation point for alias to std types

Let's say I am writing some generic algorithm in lib namespace that calls a customisation point my_func.
First attempt is using ADL for my_func
one of the user wants to specialise my_func for his type, which is an alias to std type. Surely define it in his namespace won't work because ADL won't work for alias. Defining it in std namespace is not allowed by the standard. the only option left seems to define in the algorithm's namespace lib. But this doesn't work either if the end user includes the algorithm header before including the customisation header.
#include <iostream>
#include <array>
// my_algorithm.hpp
namespace lib{
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{
// this is working as expected (ADL)
friend void my_func(const Foo1&){
std::cout << "called user1's customisation\n";
}
};
} // namespace user1
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
// this won't work because Foo2 is actually in std namespace
void my_func(const Foo2&){
std::cout << "called user2's customisation\n";
}
} // namespace user2
/* surely this isn't allowed
namespace std{
void my_func(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
} //namespace std
*/
// another attempt to costomize in the algorithm's namespace
// this won't work because my_func isn't seen before my_algorithm
namespace lib{
void my_func(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
}
// main.cpp
// #include "algorithm.hpp"
// #include "user1.hpp"
// #include "user2.hpp"
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}
https://godbolt.org/z/bfdP8s
Second attempt is using niebloids for my_func, which has the same problem as ADL.
Third attempt is using tag_invoke, which should have same problem as ADL, i.e.,
customising in user namespace won't work because my type is an alias to std type
customising in std isn't allowed
customising in lib namespace depends on the order the header includes
The first points seem to be true, but the last point isn't. This seems to work
#include <iostream>
#include <array>
// tag_invoke.hpp overly simplified version
namespace lib_ti{
inline namespace tag_invoke_impl{
inline constexpr struct tag_invoke_fn{
template<typename CP, typename... Args>
decltype(auto) operator()(CP cp, Args&&... args) const{
return tag_invoke(cp, static_cast<Args&&>(args)...);
}
} tag_invoke{};
} // namespace tag_invoke_impl
} // namespace lib_to
// my_algorithm.hpp
// #include "tag_invoke.hpp"
namespace lib{
inline constexpr struct my_func_fn {
template <typename T>
void operator()(const T& t) const{
lib_ti::tag_invoke(*this, t);
}
} my_func{};
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{
// this is working as expected (ADL)
friend void tag_invoke(lib::my_func_fn, const Foo1&){
std::cout << "called user1's customisation\n";
}
};
} // namespace user1
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
// this won't work because Foo2 is actually in std namespace
void tag_invoke(lib::my_func_fn, const Foo2&){
std::cout << "called user2's customisation\n";
}
} // namespace user2
/* surely this isn't allowed
namespace std{
void tag_invoke(lib::my_func_fn, const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
} //namespace std
*/
// another attempt to customise in the algorithm's namespace
// In ADL case, this does not work. But in this case, it seems to work. why?
namespace lib{
void tag_invoke(lib::my_func_fn, const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
}
// main.cpp
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}
https://godbolt.org/z/hsKbKE
Why does this not have the same problem as the First one (raw ADL)?
Forth attempt is using template specialisation, which seems to work normally as expected
#include <iostream>
#include <array>
// my_algorithm.hpp
namespace lib{
template<typename T, typename = void>
struct my_func_impl{
//void static apply(const T&) = delete;
};
inline constexpr struct my_func_fn {
template <typename T>
void operator()(const T& t) const{
using impl = my_func_impl<std::decay_t<T>>;
impl::apply(t);
}
} my_func{};
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{};
} // namespace user1
namespace lib{
template<>
struct my_func_impl<user1::Foo1>{
void static apply(const user1::Foo1&){
std::cout << "called user1's customisation\n";
}
};
} //namespace lib
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
} // namespace user2
namespace lib{
template<>
struct my_func_impl<user2::Foo2>{
void static apply(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
};
}
// main.cpp
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}
https://godbolt.org/z/r71x6c
What is the best way to write generic algorithms and customisation points and allow clients to customise for aliases for std types?
one of the user wants to specialise my_func for his type, which is an alias to std type
This is the original sin, which is causing you all the pain. Type aliases in C++ are just aliases; they're not new types. You have a generic algorithm that uses a customization point, something like
// stringify_pair is my generic algorithm; operator<< is my customization point
template<class T>
std::string stringify_pair(K key, V value) {
std::ostringstream oss;
oss << key << ':' << value;
return std::move(oss).str();
}
Your user wants to call this generic algorithm with a standard type, like
std::string mykey = "abc";
std::optional<int> myvalue = 42;
std::cout << stringify_pair(mykey, myvalue);
This doesn't work because std::optional<int> doesn't provide an operator<<.
It can't possibly be made to work, because your user doesn't own the std::optional<int> type and therefore can't add operations to it. (They can certainly try, physically speaking; but it doesn't work from a philosophical point of view, which is why you keep running into roadblocks every time you get (physically) close.)
The simplest way for the user to make their code work is for them to "take legal ownership" of the type definition, instead of relying on somebody else's type.
struct OptionalInt {
std::optional<int> data_;
OptionalInt(int x) : data_(x) {}
friend std::ostream& operator<<(std::ostream&, const OptionalInt&);
};
OptionalInt myvalue = 42; // no problem now
You ask why tag_invoke doesn't have the same problem as raw ADL. I believe the answer is that when you call lib::my_func(t), which calls lib_ti::tag_invoke(*this, t), which does an ADL call to tag_invoke(lib::my_func, t), it's doing ADL with an argument list that includes both your t (which doesn't really matter) and that first argument of type lib::my_func_fn (which means lib is an associated namespace for this call). That's why it finds the tag_invoke overload you put into namespace lib.
In the raw ADL case, namespace lib is not an associated namespace of the call to my_func(t). The my_func overload you put into namespace lib is not found, because it isn't found by ADL (not in an associated namespace) and it isn't found by regular unqualified lookup either (because waves hands vaguely two-phase lookup).
What is the best way to write generic algorithms and customisation points and allow clients to customise for aliases for std types?
Don't. The "interface" of a type — what operations it supports, what you're allowed to do with it — is under the control of the author of the type. If you're not the author of the type, don't add operations to it; instead, create your own type (possibly by inheritance, preferably by composition) and give it whatever operations you want.
In the worst case, you end up with two different users in different parts of the program, one doing
using IntSet = std::set<int>;
template<> struct std::hash<IntSet> {
size_t operator()(const IntSet& s) const { return s.size(); }
};
and the other one doing
using IntSet = std::set<int>;
template<> struct std::hash<IntSet> {
size_t operator()(const IntSet& s, size_t h = 0) const {
for (int i : s) h += std::hash<int>()(i);
return h;
}
};
and then both of them try to use std::unordered_set<IntSet>, and then boom, ODR violation and undefined behavior at runtime when you pass a std::unordered_set<IntSet> from one object file to another and they agree on the name of std::hash<std::set<int>> but disagree on its meaning. It's just a huge can of worms. Don't open it.

Overload resolution in nested namespace with parent namespace

I thought that in a nested namespace, anything that is part of the parent (or global) namespace is considered equally for overload resolution, but this example seems to show otherwise.
This works fine:
#include <iostream>
void foo(int) { std::cout << "int\n"; }
void foo(float) { std::cout << "float\n"; }
namespace NS {
void bar() {
foo(0);
}
}
int main() {
NS::bar();
}
The call to foo(0) matches foo(int) since it is a better match and everything works as expected. However, if I move the declaration of foo(float) into the namespace:
#include <iostream>
void foo(int) { std::cout << "int\n"; }
namespace NS {
void foo(float) { std::cout << "float\n"; }
void bar() {
foo(0);
}
}
int main() {
NS::bar();
}
The call to foo(0) now calls foo(float)!
I have searched through https://en.cppreference.com/w/cpp/language/overload_resolution and many other such pages to find the rule that explains this, but I seem to be missing it. Can someone please explain which of the many complex overload resolution rules causes this, or is it something else?
EDIT
I just discovered it is even weirder. Even if the foo inside the namespace doesn't match at all, it still won't use the one outside. This just completely fails to compile:
#include <iostream>
void foo(int) { std::cout << "int\n"; }
namespace NS {
void foo(float, float) { std::cout << "float\n"; }
void bar() {
foo(0);
}
}
int main() {
NS::bar();
}
The point is name lookup, which happens before overload resolution.
When the name foo is found at the namespace NS, name lookup stops, the further scopes won't be checked, the global foo won't be found at all. Then there's only one candidate in overload resolution, and int could convert to float implicitly, then NS::foo(float) gets called at last.
(emphasis mine)
name lookup examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.

Template Resolution in <Inside The C++ Object Model>

In Chapter 7 of "Inside the C++ Object Model," it was written that the resolution of a nomember name depends on whether the use of name is related to "the type of parameter used to instantiate the template."
I write a test:
/// -------------Test.h---------------
#ifndef TEST_H
#define TEST_H
#include <iostream>
using namespace std;
extern double foo(double t);
template <typename T>
class Test {
public:
void fun1() {
member = foo(val);
}
T fun2() {
return foo(member);
}
private:
int val;
T member;
};
#endif
and
/// -------------test1.cc-------------
#include <iostream>
using namespace std;
double foo(double t) {
cout << "foo doule is called" << endl;
return t;
}
int foo(int t) {
cout << "foo int is called" << endl;
return t;
}
-------------test.cc--------------
#include "Test.h"
extern int foo(int t);
int main() {
Test<int> fi;
fi.fun1();
fi.fun2();
return 0;
}
I expect "foo double is called\n
foo int is called",
but I got "foo double is called\n
foo double is called".
My g++ version is bellow. I'll be appreciate it if you can help me.
I'm afraid the book is not painting the complete picture (and it's a bit aged). Yes, foo(member) is a function call that is dependent on the template parameter. But the specific way in which functions are looked up in templates is described in the C++ standard at [temp.dep.candidate]:
For a function call where the postfix-expression is a dependent name,
the candidate functions are found using the usual lookup rules
([basic.lookup.unqual], [basic.lookup.argdep]) except that:
For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either
the template definition context or the template instantiation context
are found.
foo's overloads can be looked up in one of two ways. By direct unqualified lookup, and by argument dependent lookup (aka ADL). Simple unqualified lookup considers only the names that are known at the point of the template definition. Since you only declared foo(double), that is the only overload found at the point of the template definition.
At the point of instantiation, the compiler will try to do ADL to find more foo's, but fundamental types don't contribute to ADL. int cannot be used to find foo(int). So the compiler can do only one thing, convert the integer to a double, and call foo(double).
If you want to test your compilers ADL, you just need to add a simple user defined type and overload. For instance:
enum E{};
E foo(E) {
cout << "foo E is called\n";
return {};
}
int main() {
Test<E> fi;
fi.fun1();
fi.fun2();
return 0;
}

Function overload resolution trouble

Over the weekend, I had specific problem with function overload resolution that I can't seem to solve.
The code below is a distillation of the problem:
#include <iostream>
using namespace std;
template<typename T>
void f(const T& t)
{
cout << "In template function." << endl;
}
class C
{
public:
void f() { cout << "In class function." << endl; }
void g() { int i=0; f(i); }
void h() { int i=0; f<int>(i); }
void i() { extern void f(int); int i=0; f(i); }
};
int main()
{
cout << "Test" << endl;
C c;
c.i();
return 0;
}
1) C::g won't compile because the compiler won't try the template. It just complains that there is no C::f to match.
2) C::h won't compile for no reason that is obvious to me. The message is "expected primary-expression before 'int'"
3) C::i will compile, but (after commenting out g and h) it won't link to anything. I think I understand this: the extern is forcing the linker to look into another compilation unit, but any template definition would be in this compilation unit.
I would appreciate any clarification on the reasons for 1 and 2. Also, ultimately, can someone suggest a way to get this to work that doesn't involve creating another compilation unit?
Likely it is finding C::f instead of global f. Use ::f<int>(i).
Look here: http://ideone.com/zs9Ar
Output:
Test
In template function.
#include <iostream>
using namespace std;
template<typename T>
void f(const T& t)
{
cout << "In template function." << endl;
}
class C
{
public:
void f() { cout << "In class function." << endl; }
void g() { using ::f; int i=0; f(i); }
};
int main()
{
cout << "Test" << endl;
C c;
c.g();
return 0;
}
Names in a smaller scope hide names in an outer scope.
Use ::f to indicate global namespace.
In i() you declare a function called f, but indeed there is no definition (it is not the same as the template), so you get a linker error.
C++ is somewhat "greedy" when trying to find functions. As soon as it finds a matching function name in the current scope (in this case, class C) it stops and then tries to match the parameters. If the parameters don't match, it won't try additional scopes. I assume this was to keep compiler writers sane. That's why g doesn't compile.
The same thing applies to h. It sees a non-template function f in the class scope and gets confused. It thinks you're trying to do a < comparison against the function C::f.
In both cases you can use ::f to qualify the name to the global scope.
Functions with the same name will hide other functions of the same name when they have different scopes, even if they have different signatures.
Instead of using a template for the first f, you could just do f(int) and the result would be the same for #1(the C::f() hides the ::f(int)).
One solution is to add "::" before the name to force the global namespace.
Another solution would be to change the names.

Weird behaviour of Koenig Lookup

consider the following program:
namespace NS2 {
class base { };
template<typename T>
int size(T& t) {
std::cout << "size NS2 called!" << std::endl;
return sizeof(t);
}
};
namespace NS1 {
class X : NS2::base { };
}
namespace NS3 {
template<typename T>
int size(T& t) {
std::cout << "size NS3 called!" << std::endl;
return sizeof(t) + 1;
}
template<typename T>
class tmpl
{
public:
void operator()() { size(*this); }
};
};
int main() +{
NS3::tmpl<NS1::X> t;
t();
return 0;
}
My compiler (gcc 4.3.3) does not compile the program because the call to size is ambigous. The namespace NS2 seems to be added to the set of associate namespaces for the size call in the class tmpl. Even after reading the section about Koenig Lookup in the ISI Standard I am not sure if this behaviour is standard conform. Is it?
Does any one know a way to work around this behaviour without qualifying the size call with the NS3 prefix?
Thanks in advance!
Template arguments and base classes both affect ADL, so I think GCC is correct, here: NS3 comes from the current scope, NS1 from the X template argument, and NS2 from the base class of the template argument.
You have to disambiguate somehow; I'd suggest renaming one or more of the functions, if feasible, or perhaps use SFINAE to disambiguate the functions.
(Similar Situation: Note that boost::noncopyable is actually "typedef noncopyable_::noncopyable noncopyable;" so that the boost namespace doesn't get added to the ADL set of types that derive from it.)