I have the following function
template <typename T, typename U>
const T* visitor_fct(U& operand)
{
return (boost::get<T>(&operand));
}
When I do
boost::variant<int, std::string> str = "string";
std::cout << visitor_fct<std::string>(str) << std::endl;
I get the correct output
But when I change the declaration of str to :
boost::variant<int, std::string, bool> str = "toto";
I am always getting a nullptr;
Why ?
The reason is that a string literal (char*)converts to bool better than to std::string so your string literal doesn't initialize the string component of the variant, but rather than bool component (to true).
See the following which outputs bool 1:
#include <iostream>
void foo(bool b)
{
std::cout << "bool " << b << std::endl;
}
void foo(std::string s)
{
std::cout << "string " << s << std::endl;
}
int main()
{
foo("Bar");
}
Initializing with std::string("toto") will solve your problem.
4.12/1 shows us the conversion in question:
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a
prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false;
any other value is converted to true. A prvalue of type std::nullptr_t can be converted to a prvalue of
type bool; the resulting value is false.
[As noted also in the other answer] This implicit conversion takes precedence over the converting constructor of std::string and so is selected, causing the type used in the variant to be bool.
What seems to be happening here is because there's a bool present in the boost::variant, the const char* you're passing is no longer used for the string but rather is converted to bool.
if you change this line:
boost::variant<int, std::string, bool> str = "toto";
to this:
boost::variant<int, std::string, bool> str = std::string("toto");
it will work.
Here's why the bool gets chosen over the string: implicit conversions happen between any types of pointer and bool, and conversions between built-in types are always preferred to user-defined conversions. And since std::string is a user defined type (standard mind you but still user defined) the bool wins over the string.
Related
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.
I have these function pointer types:
typedef int(*a)(char*);
typedef const int(*b)(char*);
typedef int(*c)(char* const);
typedef int(*d)(const char*);
typedef long(*e)(char*);
typedef int(*f)(unsigned char*);
typedef void(*g)(char*);
I know that a and c are exactly the same (at least in C++), as the const is ignored in function prototype argument types.
My question is if I have a variable of type a, and another variable of any of those 7 types, which of them would I be able to assign to the first variable?
a foo = NULL;
(a/b/c/d/e/f/g) bar = ...;
foo = bar; // Is this UB based on the type of bar?
Would I be able to detect it?
I tried taking advantage of how template<class F> ::std::function::operator=(F&&) is defined, as "This operator does not participate in overload resolution unless f is Callable for argument types Args... and return type R."
#include <iostream>
#include <functional>
#include <type_traits>
template<class T, class U>
static void _print_is_assignable(const char* const t_name, const char* const u_name) {
using function_t = ::std::function<typename ::std::remove_pointer<T>::type>;
std::cout << t_name;
std::cout << (::std::is_assignable<function_t&, U>::value ? " == " : " != ");
std::cout << u_name << '\n';
}
#define PRINT_IS_ASSIGNABLE(T, U) _print_is_assignable<T, U>(#T, #U)
typedef int(*a)(char*);
typedef const int(*b)(char*);
typedef int(*c)(char* const);
typedef int(*d)(const char*);
typedef long(*e)(char*);
typedef int(*f)(unsigned char*);
typedef void(*g)(char*);
int main() {
PRINT_IS_ASSIGNABLE(a, a); // a == a
PRINT_IS_ASSIGNABLE(a, b); // a == b
PRINT_IS_ASSIGNABLE(a, c); // a == c
PRINT_IS_ASSIGNABLE(a, d); // a == d
PRINT_IS_ASSIGNABLE(a, e); // a == e
PRINT_IS_ASSIGNABLE(a, f); // a != f
PRINT_IS_ASSIGNABLE(a, g); // a != g
PRINT_IS_ASSIGNABLE(g, a); // g == a
}
I've read the spec a bit, and the closest thing I could find is in section 5.2.10 [expr.reinterpret.cast]:
A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined. Except that converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
Which seems to tell me that, no, it is undefined behaviour, unless the function pointer types are exactly the same, and I would have to cast back to the original type.
With some testing, g++ doesn't seem to mind if pointers are returned what pointers are returned, and if the return type is const qualified doesn't matter, which makes sense. I would just have to use std::function when trying to deal with almost compatible types.
I have below code snippet where i declare a variable called pval which is attempting to derive T&& on a T* [ with T being int ]. As per type information [ decoded using abi ] the type derived is int*.
But when I compare the int* type with decltype(pval) it returns zero rather than 1 which means it treats pval as different type other than int*. So which one is the wrong pval being int* as reported by typeid or is_same which indicates comparison as false.
#include<iostream>
#include<string>
#include<typeinfo>
#include<cxxabi.h>
#include<type_traits>
using namespace std;
std::string classname(const std::type_info& info)
{
int status;
char* rslt=abi::__cxa_demangle(info.name(),0,0,&status);
std::string result(rslt);
free(rslt);
return result;
}
int main(int argc, char* argv[])
{
int* ptr = new int(10);
decltype(std::move(ptr)) pval = std::move(ptr);
cout << classname(typeid(pval)) << endl; // as per typeid information the type of pval is int*.
bool isSame = is_same<decltype(pval), int*>::value; // What then is the pval not same as int* as per is_same ?
cout << "isSame status = " << isSame << endl;
cout << is_same<int*, int*>::value << endl;
return(0);
}
The behaviors of decltype and typeid are different.
The exact type of pval is int* &&, i.e. an rvalue-reference to int*. (That's why std::is_same returns false when comparing it with the type of int*.) According to the behavior of decltype,
if the value category of expression is xvalue, then decltype yields T&&;
And what std::move(ptr) returns is an xvalue.
The following expressions are xvalue expressions:
a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);
Then given decltype(std::move(ptr)) pval, the type of pval would be int* &&.
On the other hand, the behavior of typeid is different.
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.
That means the std::type_info object returned by typeid(pval) will refer to the referenced type, i.e. int*, not int* &&.
BTW: What std::type_info::name returns is implementation defined.
The __cxa_demangle() function doesn't give you reliable (or any?) information about const and reference qualifiers. Try this instead of your classname() function:
template <typename T, bool WithCVCorrections = true>
std::string type_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own(
abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr),
std::free
);
std::string r = (own != nullptr) ? own.get() : typeid(TR).name();
if (WithCVCorrections) {
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += "&";
else if (std::is_rvalue_reference<T>::value)
r += "&&";
}
return r;
}
... which is based on Howard Hinnant's code here. Obvious caveat: This will only work for some compilers (not MSVC).
In this line:
auto a = "Hello World";
What is the exact Type of a? I'd guess char[] or const char* const but I'm not sure.
N4296 2.13.5/8
Ordinary string literals and UTF-8 string literals are also referred
to as narrow string literals. A narrow string literal has type “array
of n const char”, where n is the size of the string as defined below,
and has static storage duration (3.7).
But since variable is initialized as in your code it is actually const char*, you can check it like this.
template<typename> struct TD;
int main()
{
auto a = "Hello World";
TD<decltype(a)> _;
}
Here will be compile error in which you can see the actual type of TD instance, something like this with clang
error: implicit instantiation of undefined template 'TD<const char *>'
N4296 7.1.6.4
If the placeholder is the auto type-specifier, the deduced type is
determined using the rules for template argument deduction.
template<typename> struct TD;
template<typename T>
void f(T)
{
TD<T> _;
}
int main()
{
auto c = "Hello";
TD<decltype(c)> _;
f("Hello");
}
Both instantiated objects of type TD has type TD<const char*>.
N4926 14.8.2.1
Template argument deduction is done by comparing each function
template parameter type (call it P) with the type of the corresponding
argument of the call (call it A) as described below.
If P is not a reference type:
If A is an array type, the pointer type produced by the
array-to-pointer standard conversion (4.2) is used in place of A for
type deduction
Unless you've reason to think it'd be implementation or un-defined, can just test:
#include <iostream>
template <typename T> void f() { std::cout << "other\n"; }
template <> void f<const char*>() { std::cout << "const char*\n"; }
template <> void f<const char* const>()
{ std::cout << "const char* const\n"; }
template <> void f<const char(&)[12]>() { std::cout << "const char[12]\n"; }
int main()
{
auto a = "Hello World";
f<decltype(a)>();
}
Output:
const char*
Checking that ++a compiles is another clue (it does), and while implementation defined #include <typeinfo> / typeid(a).name() can often help answer such questions.
Change to auto& a and you'll see a changes to const char(&)[12].
You can print the type of a using typeinfo
int main()
{
auto a = "Hello World";
std::cout << "type is: " << typeid(a).name() << '\n';
}
on gcc it will print
pi is: PKc
which stands for pointer to constant char
If you're in Windows the output will be a lot more readable, but you get used to this syntax too.
If you know more or less which type you a re looking for, you can also check if two types are equivalent with:
#include <typeinfo>
std::cout << std::is_same<const char*, decltype(a)>::value << std::endl;
I would like to automatically generate const accessor function for given member but I struggle with arrays. It is possible to "decay" array type to a pointer, but I do not know how to make type of pointed value const? Any obvious method of adding const will only apply the pointer. Of course, I can make specialised accessor for array types, but it is not ideal solution. Returning const pointer to const value would also be acceptable. This is example of incomplete accessor:
auto foo() const -> const typename std::decay<decltype(foo_)>::type { return foo_; }
If you intend to get the address of a member array, simply qualify it as const
#include <iostream>
using namespace std;
struct fooType {
};
class MyClass {
public:
fooType foo_[2];
auto foo() const -> typename std::decay<const decltype(foo_)>::type
{ return &foo_[0]; }
};
int main() {
MyClass classObj;
classObj.foo();
return 0;
}
http://ideone.com/PjclAf
Edit:
The documentation states that
Applies lvalue-to-rvalue, array-to-pointer, and function-to-pointer
implicit conversions to the type T, removes cv-qualifiers, and defines
the resulting type as the member typedef type. This is the type
conversion applied to all function arguments when passed by value.
(emphasis mine)
The important takeaway here is that std::decay() always act to "simulate" a pass-by-value mechanism with the type you're feeding it. Cv-qualifiers are dropped iff they can be dropped in a pass-by-value call, not if they actually define the resulting type.
Take the following example:
#include <iostream>
#include <type_traits>
template <typename T, typename U>
struct decay_equiv :
std::is_same<typename std::decay<T>::type, U>::type
{};
void function1(int happyX) {
// happyX can be modified, it's just a local variable
happyX = 42;
std::cout << happyX << std::endl;
}
void function2(const int *ptrByValue) {
// ptrByValue can be modified, however its type is 'const int' and that CANNOT be modified
ptrByValue = (const int*)0xDEADBEEF;
std::cout << ptrByValue << std::endl;
}
int main()
{
std::cout << std::boolalpha
<< decay_equiv<const int, int>::value << std::endl // cv-qualifiers are dropped (pass-by-value)
<< decay_equiv<const int[2], int*>::value << std::endl; // cv-qualifiers here CANNOT be dropped, they're part of the type even if passed by value
const int myConstValue = 55;
function1(myConstValue);
const int myArrayToConstValues[2] = {4,2};
function2(myArrayToConstValues);
return 0;
}
http://ideone.com/AW6TJS
In your example you're asking for a constant return value (you can't modify the address of the first element) but asking in the trailing return type for a non-const one, that's why the compiler is complaining and what I just wrote is the reason why the const cannot be dropped by std::decay(): it is part of the type even in a pass-by-value situation (e.g. function2()).