A minimized example of my code showing the problem:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
template <typename T>
const std::map<std::string, T> smap;
template <>
const std::map<std::string, bool> smap<bool>{{"a", false}};
int main() {
std::map<bool, std::string> rmap{{false, "x"}};
for (const auto& [key, val] : rmap) {
std::cerr << typeid(bool).hash_code() << "\n";
std::cerr << typeid(decltype(key)).hash_code() << "\n";
std::cerr << smap<bool>.size() << "\n";
std::cerr << smap<decltype(key)>.size() << "\n";
assert((std::is_same_v<bool, decltype(key)>));
}
return 0;
}
Godbolt
It gives the output:
10838281452030117757
10838281452030117757
1
0
example.cpp:22: int main(): Assertion `(std::is_same_v<bool, decltype(key)>)' failed.
Why is it that I can't access the variable template using decltype when it's referring to the same type (bool)?
For the record I also tried to not use structured binding and using decltype on first in the pair with the same result.
However if I create an actual bool variable, like so ...
bool b;
std::cerr << settings_map<decltype(b)>.size() << "\n";
... it's working.
decltype(key) is const bool, not bool. And typeid strips const qualifiers, so the two have the same (runtime) representation.
If the type of type or expression is cv-qualified, the result of the typeid refers to a std::type_info object representing the cv-unqualified type (that is, typeid(const T) == typeid(T)).
So while typeid treats the two types as equivalent, template expansion (and is_same_v) does not, and you get two different maps: one for bool and one for const bool. Note that the assertion
assert((std::is_same_v<const bool, decltype(key)>));
succeeeds if put in place of the one in your code. To remove cv-qualifiers, use remove_cv_t.
std::cerr << settings_map<std::remove_cv_t<decltype(key)>>.size() << "\n";
In your last snippet
bool b;
std::cerr << settings_map<decltype(b)>.size() << "\n";
The b is not constant, so decltype(b) is actually bool, not const bool.
Keys are const so you'll need to remove it to make the assertion pass.
assert((std::is_same_v<bool, std::remove_cv_t<decltype(key)>>));
the value_type is std::pair<const Key, T>, so decltype(key) is const bool, not bool
and you capture it by const auto& so it's const anyway
typeid remove all cvref qualifiers, that's why you get same hash_code
as a side note, hash_code is a hash and no guaranteed on they would not collide, you can use operator== to check type equality.
Related
I wrote a simple SFINAE-based trait to detect whether a class has a method with a specific signature (the back story is that I am trying to detect whether a container T has a method T::find(const Arg&) - and if yes, then store a pointer to this method).
After some testing I discovered a case where this trait would give a false positive result - if T defines the method as a template but definition compiles not for all possible Ts. This is best demonstrated with an example:
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
using namespace std;
// HasFooWithArgument is a type trait that returns whether T::foo(const Arg&) exists.
template <typename T, typename Arg, typename = void>
inline constexpr bool HasFooWithArgument = false;
template <typename T, typename Arg>
inline constexpr bool HasFooWithArgument<T, Arg, std::void_t<decltype(std::declval<const T&>().foo(std::declval<const Arg&>()))>> = true;
// Below are the test cases. struct E is of the most interest.
struct A {};
struct B {
void foo(int x) const {}
};
struct C {
void foo(int x) const {}
void foo(const std::string& x) const {}
};
struct D {
template <typename T>
void foo(const T& x) const {}
};
struct E {
// Any T can be substituted (hence SFINAE "detects" the method)
// However, the method compiles only with T=int!
// So effectively, there is only E::foo(const int&) and no other foo methods...
template <typename T>
void foo(const T& x) const {
static_assert(std::is_same_v<T, int>, "Only ints are supported");
}
};
int main()
{
cout << std::boolalpha;
cout<<"A::foo(int): " << HasFooWithArgument<A, int> << "\n";
cout<<"B::foo(int): " << HasFooWithArgument<B, int> << "\n";
cout<<"C::foo(int): " << HasFooWithArgument<C, int> << "\n";
cout<<"C::foo(string): " << HasFooWithArgument<C, std::string> << "\n";
cout<<"C::foo(std::vector<int>): " << HasFooWithArgument<C, std::vector<int>> << "\n";
cout<<"D::foo(string): " << HasFooWithArgument<D, std::string> << "\n";
cout<<"E::foo(int): " << HasFooWithArgument<E, int> << "\n";
cout<<"E::foo(string): " << HasFooWithArgument<E, std::string> << "\n";
E e;
e.foo(1); // compiles
// e.foo(std::string()); // does not compile
return 0;
}
The above prints:
A::foo(int): false
B::foo(int): true
C::foo(int): true
C::foo(string): true
C::foo(std::vector<int>): false
D::foo(string): true
E::foo(int): true
E::foo(string): true
Unfortunately, this last case is my main target: as I mentioned above, I am doing this to detect container find methods and they are usually defined as templates to support heterogeneous lookup.
In other words, std::set<std::string, std::less<>> is where my detection gives false positives as it returns true for any argument type.
Thanks in advance!
The issue you're describing is not due to the fact that SFINAE doesn't work properly with function templates (it does). It has to do with the fact that SFINAE can't detect whether a function body is well-formed.
Your approach should work just fine when it comes to detecting that std::set<std::string> does not have a find member that takes a std::string_view argument. This is because the templated find member does not participate in overload resolution unless the set has a transparent comparator. Since std::set<std::string> doesn't have a transparent comparator, its find function will only accept types that are implicitly convertible to std::string; any attempt to pass any other type will result in an overload resolution failure, which will be detected by SFINAE just like any other case of an argument type mismatch.
Where your approach won't work is with something like std::set<std::string, std::less<>> and trying to call find with an argument type of say, int. In this case the detection will succeed because the find template is unconstrained, but then compilation will fail later on if you try to actually call find with an int argument, because somewhere in the body, it will try to call std::less<> with types that can't be compared with each other (std::string and int).
Colloquially, we say that a function or a class that makes it possible to detect using SFINAE whether it, itself, would be well-formed, is "SFINAE-friendly". In that sense, std::set::find fails to be SFINAE-friendly. To make it SFINAE-friendly, it would have to guarantee that an overload resolution failure would occur if you tried to call it with a type that isn't comparable using the specified transparent comparator.
When something is not SFINAE-friendly, the only workaround is to special-case it: in other words, you have to put in your code a special case that makes detection fail for std::set::find in this case, by checking explicitly whether the comparator accepts the argument type. (std::less<> is SFINAE-friendly: it does the overload resolution in the (explicitly specified) return type.)
How does this code not compile? Why can bs[1] not be deduced to bool?
Is there a generic way to solve this problem?
#include <iostream>
#include <string>
#include <bitset>
using namespace std;
template<typename T> struct StringConverter{};
template<> struct StringConverter<bool>{
std::string operator()(const bool &i){ return i?"true":"false"; }
};
template<typename T> std::string to_string(const T &val){
return StringConverter<T>()(val);
}
int main(){
// this does not compile
std::bitset<10> bs;
std::cout << to_string(bs[0]) << std::endl;
// this does
const std::bitset<10> bs_const;
std::cout << to_string(bs_const[0]) << std::endl;
}
Compiler Error:
main.cpp:12:12: error: type 'StringConverter<std::bitset<10>::reference>' does not provide a call operator
return StringConverter<T>()(val);
^~~~~~~~~~~~~~~~~~~~
main.cpp:18:18: note: in instantiation of function template specialization 'to_string<std::bitset<10>::reference>' requested here
std::cout << to_string(bs[0]) << std::endl;
^
1 error generated.
the non-const bitset::operator[] returns a proxy object rather than a bool (this has to be done because that proxy can be used to change the bit value). const bitset::operator[] however just returns a bool (not a reference, just a plain bool) so it matches for the StringConverter[
If you check the declaration of operator[], you'll notice it has two overloads - the const one, which returns bool and is used in your second example, and the non-const, which returns the object of type std::bitset::reference.
The latter is used for bit field modification, and it absolutely cannot be a bool& since it has to address a specific bit. The problem you ran into is quite common for these proxy return types (this is where I should mention vector<bool>).
As a possible solution you can use the fact that std::bitset::reference is convertible to bool (and is not convertible to any other conceivable type that you might use for your StringConverter specializations).
I am currently thinking about how to best constrain a generic type of a template to an std::sting as well as string literals. Therefore I compare the deduced type with the desired type using std::is_same. In case of an std::string this works right away. For a string literal, meaning a char const array, it only works if I use std::decay on the type and then compare the result to the type char const *. If I directly compare the deduced type to what I think it should be, is_same returns false, as is illustrated by the following example code.
template <class TYPE>
void function(TYPE&& parameter)
{
//this doesn't work as expected
std::cout << typeid(TYPE).name() << " : " << typeid(char const [5]).name() << std::endl;
std::cout << std::is_same<char const [5], TYPE>::value << std::endl;
//this works as expected
std::cout << typeid(std::decay_t<TYPE>).name() << " : " << typeid(char const *).name() << std::endl;
std::cout << std::is_same<char const *, std::decay_t<TYPE>>::value << std::endl;
}
int main(int argc, char** argv)
{
function("name");
return 0;
}
The output generated is the following:
char const [5] : char const [5]
0
char const * __ptr64 : char const * __ptr64
1
Now, what I am wondering is why is_same returns false in the first case even though the types appear to be identical.
The only possible explanation that I could come up with is that within the function std::is_same a transformation similar to std::decay is applied to the type (for instance a function call). But then again this transformation would also occur to the other type, yielding the same result and thus resulting in equality.
String literals are not passed by value as char const [N], but by reference as char const (&)[N].
This works correctly for me:
std::cout << std::is_same<char const (&)[5], TYPE>::value << std::endl;
Note here that
1) Refers to a std::type_info object representing the type type. If type is a reference type, the result refers to a std::type_info object representing the referenced type.
You can easily verify that is_same doesn't discard reference-ness in the same way as type_info, for example by checking that
std::is_same<int&, int>::value == false
This explains why the typeid name is the same, but your is_same test still fails.
Using gcc custom function:
template < class T >
constexpr std::string type_name()
{
std::string p = __PRETTY_FUNCTION__;
return p.substr( 43 + 10, p.length() - 100 - 1 - 10 );
}
And adding it to your code:
std::cout << type_name<TYPE>() << " : " << type_name<char const [5]>() << std::endl;
The results are:
A5_c : A5_c
0
const char (&)[5] : const char [5]
So you need to use std::remove_reference on TYPE.
It may be an mistake of the compiler. The compiler generates such types-objects given by typeid at compiletime. The compiler wouldn't compile array-types for each length (0 to 2**n), so it compiles them when needed, and it may "forget" about the duplicate. Try to use a special template where you seperate the length from the contained type. This is not the type but you can check if it is equal to another.
template<class T, size_t size>
struct array_t{};
template <class T, size_t size>
void function(T[size] parameter) {
std::cout << typeid(array_t<T, size>).name() << " : " << typeid(array_t<char, 5>).name() << std::endl;
std::cout << std::is_same<array_t<T, size>, array_t<char, 5>>::value << std::endl;
};
What's the best way to determine if an expression is a rvalue or lvalue in C++? Probably, this is not useful in practice but since I am learning rvalues and lvalues I thought it would be nice to have a function is_lvalue which returns true if the expression passed in input is a lvalue and false otherwise.
Example:
std::string a("Hello");
is_lvalue(std::string()); // false
is_lvalue(a); // true
Most of the work is already done for you by the stdlib, you just need a function wrapper:
template <typename T>
constexpr bool is_lvalue(T&&) {
return std::is_lvalue_reference<T>{};
}
in the case you pass a std::string lvalue then T will deduce to std::string& or const std::string&, for rvalues it will deduce to std::string
Note that Yakk's answer will return a different type, which allows for more flexibility and you should read that answer and probably use it instead.
I solved the above question using two overloaded template functions. The first takes as input a reference to a lvalue and return true. Whereas the second function uses a reference to rvalue. Then I let the compiler match the correct function depending on the expression passed as input.
Code:
#include <iostream>
template <typename T>
constexpr bool is_lvalue(T&) {
return true;
}
template <typename T>
constexpr bool is_lvalue(T&&) {
return false;
}
int main()
{
std::string a = std::string("Hello");
std::cout << "Is lValue ? " << '\n';
std::cout << "std::string() : " << is_lvalue(std::string()) << '\n';
std::cout << "a : " << is_lvalue(a) << '\n';
std::cout << "a+b : " << is_lvalue(a+ std::string(" world!!! ")) << '\n';
}
Output:
Is Lvalue ?
std::string() : 0
a : 1
a+b : 0
I would take a page from boost::hana and make the return value of is_lvalue encode the lvalue-ness of its argument both as a constexpr value, and as a type.
This lets you do stuff like tag dispatching without extra boilerplate.
template<class T>
constexpr std::is_lvalue_reference<T&&>
is_lvalue(T&&){return {};}
the body of this function does nothing, and the parameter's value is ignored. This lets it be constexpr even on non-constexpr values.
An advantage of this technique can be seen here:
void tag_dispatch( std::true_type ) {
std::cout << "true_type!\n";
}
void tag_dispatch( std::false_type ) {
std::cout << "not true, not true, shame on you\n";
}
tag_dispatch( is_lvalue( 3 ) );
Not only is the return value of is_lvalue available in a constexpr context (as true_type and false_type have a constexpr operator bool), but we can easily pick an overload based on its state.
Another advantage is that it makes it hard for the compiler to not inline the result. With a constexpr value, the compiler can 'easily' forget that it is a true constant; with a type, it has to be first converted to bool for the possibility of it being forgotten to happen.
Use std::is_lvalue_reference and std::is_rvalue_reference.
You don't need a wrapper if you're happy with using decltype.
std::string a("Hello");
std::is_lvalue_reference<decltype((std::string()))>::value; // false
std::is_lvalue_reference<decltype((a))>::value; // true
In C++17 you'll be able to use the following:
std::string a("Hello");
std::is_lvalue_reference_v<decltype((std::string()))>; // false
std::is_lvalue_reference_v<decltype((a))>; // true
Or you could write a wrapper as #Ryan Haining suggests, just make sure you get the types correct.
I am providing a library that supports a function bar(). What it does when you pass in a scalar value (like a double, int, whatever) is different from what happens if you pass in something that is not a scalar value (in all expected cases, a user-defined type). So I wrote code like this:
#include <iostream>
class Foo
{
public:
template <class T> void bar(T const &rhs) { std::cout << "T" << std::endl; }
void bar(double rhs) { std::cout << "double" << std::endl; }
};
int main()
{
Foo foo;
foo.bar(4);
}
The problem with this is on the second line of main(). The result of this code is output of "T". The compiler prefers the template over the call to bar(double), and I am assuming this is because the parameter is an int, which it would rather cast to int const& (since a const& can reference an r-value).
My question is "is there a way I can support every scalar value without explicitly calling them out?" I really don't want to call out every possible type, because... well... there's a lot of them. I would have to cover everything from char to long long, include every combination of volatile and unsigned, and so forth.
I know that just changing the 4 to a 4.0 works, but this is for the public interface to a library, and requiring the user to type 4.0 instead of 4 is just dirty.
Yes, with traits:
#include <type_traits>
#include <iostream>
class Foo
{
public:
template <class T>
typename std::enable_if<!std::is_scalar<T>::value, void>::type bar(T const & rhs)
{
std::cout << "T" << std::endl;
}
void bar(double rhs)
{
std::cout << "double" << std::endl;
}
};
There are six basic categories of types: scalars, functions, arrays, classes, unions and references. And void. Each of them have a corresponding trait. See here for more details.