I'm having trouble with boost::variant (using boost 1.67.0).
When my template parameter list includes both bool and std::string, any variant objects which should be treated as string, seem to be implicitly bound to bool instead. For example:
using Varval = boost::variant<bool, std::string>;
void main()
{
std::vector<Varval> vect{ true, false, "Hello_World" };
std::cout << "[ ";
for (const auto &v : vect)
std::cout << v << " ";
std::cout << "]\n";
}
Outputs:
[ 1 0 1 ]
whereas if I change nothing but the first template argument, from bool to int, it works fine:
using Varval = boost::variant<int, std::string>;
void main()
{
std::vector<Varval> vect{ true, false, "Hello_World" };
std::cout << "[ ";
for (const auto &v : vect)
std::cout << v << " ";
std::cout << "]\n";
}
Properly outputs:
[ 1 0 Hello_World ]
Any ideas?
boost::variant has one constructor overload for each stated type. In your first example there will be one overload for bool and one for std::string. You are now calling the constructor with a char[n] which can be imlicitly converted to both of them. So there is no perfect match but two candidates. But instead of telling you that the call is ambiguous, the compiler will choose the bool overload as the better match.
Why? That is already perfectly answered in this question.
In your second example with int and std::string you are passing bools and char[n] to the constructors. bool is implicitly convertable to int but not to std::string. char[n] is implicitly convertable to std::string but not to int. So the according constructors are called as there is only one candidate for each.
Related
The following code is basically a more "naive" version of the example on variant found on CPP Reference. It defines a std::variant type (line [1]) and uses std::visit to iterate through a vector of that type. Here, I used a functor (line [2]) inside the std::visit function as an attempt for a better understanding of how std::visit works.
What puzzles me the most lies in lines [4] and [5]. It appears that either Print{}, with curly brackets, or Print(), with parentheses, can serve as the first parameter to std::visit function. The later, i.e., Print(), is an instantiation and is thus supplying an object as the first parameter, which is comprehensible. I don't quite understand why the former, Print{}, works too. Can anyone shed some light on this peculiarity?
using var_t = std::variant<int, long, double, std::string>; // [1]
class Print { // [2]
public:
void operator()(int arg) {
std::cout << "Integer is " << arg << '\n';
}
void operator()(double arg) {
std::cout << "Double precision is " << arg << '\n';
}
void operator()(long arg) {
std::cout << "Long type is " << arg << '\n';
}
void operator()(const std::string& arg) {
std::cout << "String is " << arg << '\n';
}
};
std::vector<var_t> vec = {10, 15l, 1.5, "Hello, World", 900}; // [3]
for (auto& v : vec) {
//std::visit(Print{}, v); // [4]
std::visit(Print(), v); // [5]
}
Print() is a call to constructor of class Print. Print{} is list initialization of class Print. In this case the difference is only int that list initialization actually would be aggregate initialization and would init members of Print if it had any, the constructor would not.
In both cases you pass an instance of Print.
#include <iostream>
template<typename T = char>
T cast(T in){
return in;
}
int main(){
std::cout << cast<>(5) << std::endl;
return 0;
}
Above will print 5 instead of an empty character, as the function is supposed to return a character by default and not an int. What am I ding wrong?
Edit:
Forcing it with std::cout << cast<char>(5) << std::endl; shows an empty character.
The declaration of 5 is an integer by default. This causes 'T' to be overridden with the type int, rather than using your default type. If you really wanted the char of value 5 (which you probably don't), you could specify it as '\x5'.
For the ascii character 5....
int main(){
std::cout << cast('5') << std::endl;
return 0;
}
Default types in templates tend to be useful when it's not easy to determine the template type, e.g. cast from int
template<typename T = char>
T cast(int v){
return T(v);
}
and now this will default to a method that casts an int to a char (rather than a int to int).
std::cout << cast(53) << std::endl;
This question is already asked most likely, but I did not find the answer.
The code below compiles with gcc but crashes at runtime, with std::length_error (live).
void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }
//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }
int main()
{
test({"one", "two"});
}
The ability to create a string from the initializer list of strings seems controversial and, for example, does not make it possible to create the overload commented out in the code above.
But even if such construction is allowed, why does it lead to a failure?
It calls
string(const char* b, const char* e)
string ctor overload.
It works only if b and e points to the same string literal. Otherwise it is undefined behaviour.
For starters there is no used the constructor that accepts an initializer list because such a constructor looks like
basic_string(initializer_list<charT>, const Allocator& = Allocator());
^^^^^
So the compiler searches another appropriate constructor and it finds such a constructor. It is the constructor
template<class InputIterator>
basic_string(InputIterator begin, InputIterator end, const Allocator& a = Allocator());
That is the expressions "one" and "two" are considered as iterators of the type const char *.
So the function test has undefined behavior.
You could write for example (provided that string literals with the same content are stored as one string literal in memory, which is not guaranteed and depends on the selected compiler options).
#include <iostream>
#include <string>
void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }
//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }
int main()
{
test({ "one", "one" + 3 });
}
And you will get a valid result.
string overload: one
Pay attention to that this construction
{ "one", "two" }
is not an object of the type std::initializer_list<T>. This construction does not have a type. It is a braced-init-list that is used as an initialzer. Simply the compiler tries at first to use a constructor that have the first parameter of the type std::initializer_list to use with this initializer.
For example if you will use the class std::vector<const char *> then indeed the compiler will use its constructor with std::initializer_list and correspondingly initializes its parameter with this braced-init-list. For example
#include <iostream>
#include <vector>
int main()
{
std::vector<const char *> v( { "one", "two" } );
for ( const auto &s : v ) std::cout << s << ' ';
std::cout << '\n';
}
I have problem, because in the class I want to have multiple operator[] for different types of variables.
type operator[](const std::string val);
//type operator[](const long int val);
type operator[](const double val);
//type operator[](const char val);
But this way is wrong, because compiler returns error of multiple declaration (char, int and double is "the same" variable). Program is working only if 2 positions was commented. But if I used the operator[] with letter 'a', it uses the operator[](double) ('a' becomes 97.000000).
Does somebody have an idea to solve it?
At first some critics: Your exposed sample code is not an MCVE and leaves much room for speculation. (This might be one reason for all these down-votes.)
However, as this raised my attention I want to add my 2 cents.
I fully agree with scohe001 and M.M – the exposed declarations are not a problem.
It becomes a problem if the operator[]() is used with an argument of not exact matching type.
Demonstration:
#include <iostream>
#include <set>
#include <sstream>
#include <string>
// trying to resemble the OP
template <typename VALUE>
class MultiAccessorT {
public:
typedef VALUE Value;
typedef std::set<Value> Table;
private:
Table &_tbl;
public:
MultiAccessorT(Table &tbl): _tbl(tbl) { }
typename Table::iterator operator[](const std::string &str)
{
std::cout << "using operator[](const std::string&) ";
std::istringstream cvt(str);
Value value;
return cvt >> value ? _tbl.find(value) : _tbl.end();
}
typename Table::iterator operator[](long value)
{
std::cout << "using operator[](long) ";
return _tbl.find((Value)value);
}
typename Table::iterator operator[](double value)
{
std::cout << "using operator[](double) ";
return _tbl.find((Value)value);
}
typename Table::iterator operator[](char value)
{
std::cout << "using operator[](char) ";
return _tbl.find((Value)value);
}
};
// checking out
int main()
{
// build some sample data
std::set<unsigned> tbl = { 0, 2, 4, 6, 8, 10, 12, 14 };
// template instance and instance
MultiAccessorT<unsigned> acc(tbl);
// test the operators
std::cout << "MultiAccessorT::operator[](const string&): ";
std::cout << (acc[std::string("6")] != tbl.end() ? "found." : "missed!") << std::endl;
std::cout << "MultiAccessorT::operator[](long): ";
std::cout << (acc[6L] != tbl.end() ? "found." : "missed!") << std::endl;
std::cout << "MultiAccessorT::operator[](double): ";
std::cout << (acc[6.0] != tbl.end() ? "found." : "missed!") << std::endl;
std::cout << "MultiAccessorT::operator[](char): ";
std::cout << (acc['\6'] != tbl.end() ? "found." : "missed!") << std::endl;
// done
return 0;
}
I compiled and tested it with g++ on cygwin:
$ g++ --version
g++ (GCC) 6.4.0
$ g++ -std=c++11 -o test-polymorphy test-polymorphy.cc
$ ./test-polymorphy
MultiAccessorT::operator[](const string&): using operator[](const std::string&) found.
MultiAccessorT::operator[](long): using operator[](long) found.
MultiAccessorT::operator[](double): using operator[](double) found.
MultiAccessorT::operator[](char): using operator[](char) found.
$
To prevent ambiguities (which could break the code), I carefully provided matching arguments to the MultiAccessorT::operator[]() calls:
std::string("6") for operator[](const std::string&)
6L for operator[](long)
6.0 for operator[](double)
'\6' for operator[](char)
The backslash encodes an octal number to achieve a char with value 6 which is actually not printable.
To get back to the assumption of ambiguity problems I tried some counter examples:
This works:
std::cout << "using a constant string \"6\" (type const char[2]) ";
std::cout << (acc["6"] != tbl.end() ? "found." : "missed!") << std::endl;
Output was:
using a constant string "6" (type const char[2]) using operator[](const std::string&) found.
This fails:
std::cout << "using an int constant 6";
std::cout << (acc[6] != tbl.end() ? "found." : "missed!") << std::endl;
The g++ complained:
test-polymorphy.cc: In function 'int main()':
test-polymorphy.cc:66:24: error: ambiguous overload for 'operator[]' (operand types are 'MultiAccessorT' and 'int')
std::cout << (acc[6] != tbl.end() ? "found." : "missed!") << std::endl;
^
test-polymorphy.cc:26:34: note: candidate: typename MultiAccessorT::Table::iterator MultiAccessorT::operator[](long int) [with VALUE = unsigned int; typename MultiAccessorT::Table::iterator = std::_Rb_tree_const_iterator]
typename Table::iterator operator[](long value)
^~~~~~~~
test-polymorphy.cc:32:34: note: candidate: typename MultiAccessorT::Table::iterator MultiAccessorT::operator[](double) [with VALUE = unsigned int; typename MultiAccessorT::Table::iterator = std::_Rb_tree_const_iterator]
typename Table::iterator operator[](double value)
^~~~~~~~
test-polymorphy.cc:38:34: note: candidate: typename MultiAccessorT::Table::iterator MultiAccessorT::operator[](char) [with VALUE = unsigned int; typename MultiAccessorT::Table::iterator = std::_Rb_tree_const_iterator]
typename Table::iterator operator[](char value)
^~~~~~~~
Here we are. As there is no exact matching operator[](int), the compiler looks for possible operators which can be used if an implicit conversion would be applied to int. As the above example shows, there are actually three candidates – an ambiguity.
Back to the first counter example. I remember that I once ran into a terrible issue concerning this.
To illustrate this I will introduce yet another operator[]():
// This resembles my personal "hair-killer":
typename Table::iterator operator[](bool value)
{
std::cout << "using operator[](bool) ";
return value ? _tbl.begin() : _tbl.end();
}
and compile and run again:
$ g++ -std=c++11 -o test-polymorphy test-polymorphy.cc
$ ./test-polymorphy
MultiAccessorT::operator[](const string&): using operator[](const std::string&) found.
MultiAccessorT::operator[](long): using operator[](long) found.
MultiAccessorT::operator[](double): using operator[](double) found.
MultiAccessorT::operator[](char): using operator[](char) found.
using a constant string "6" (type const char[2]) using operator[](bool) found.
$
Huh, what!? using operator[](bool)?
First, there is no complained ambiguity. Second, it prefers bool over std::string? How comes?
This is really defined behavior according to C++ standard. bool is a primary type – class std::string is not. The string constant "6" is compiled to data of type const char[2] – an array. Arrays may not be passed as arguments – they implicitly decay to a pointer in this case i.e. it becomes a const char*. Now, the compiler checks the available operators (in the first round with primary argument types) and there is only one matching: operator[](bool). The other matching operator[](const std::string&) is even not recognized as it does not start the second round (with extended search including programmed types as well) after having found an unambiguous "hit" in the first.
This is something which happened to me some years ago. At first, I even didn't recognize it but after some time of testing I realized that some detail was somehow wrong. Digging deeper I finally found out exactly such an issue. (This knocked me very hard that time – and probably is responsible that I lost some of my hair.)
Last but not least, I have uploaded the final source code on ideone.com.
Minimal working example:
#include <tuple>
struct example
{
example(int, char) {}
};
int main()
{
std::tuple<example, int, double>
my_tuple(example(0, 'x'), 42, .0);
// std::tuple t = make_my_tuple(0, 'x');
return 0;
}
This works.
Is there a more elegant way to initialize only the first member, like I sketched in the comment? One which only takes the arguments to construct the first tuple member and does not initialize the others?
The reason I ask? I am just interested in the semantics of the language.
You say that giving values for the other two members is not necessary - are you worried about performance? Or that there may be no suitable value for these members?
If it's the latter, you could have your tuple hold boost::optionals. e.g.
#include <tuple>
#include <boost/optional.hpp>
using namespace boost;
struct example
{
example(int, char) {}
};
typedef std::tuple<example, optional<int>, optional<double>> MyTuple;
int main()
{
MyTuple my_tuple(example(0, 'x'), optional<int>(), optional<double>());
return 0;
}
You now semantically have the int and float "uninitialised", and can query their value as such.
To make this more elegant, you can wrap this into a function, using the perfect forwarding idiom for the arguments (in general; in this case your arguments are cheap to copy, so no speed benefit from doing this):
template <class... Args>
MyTuple make_mytuple(Args&&... args)
{
return MyTuple(example(std::forward<Args>(args)...), optional<int>(), optional<double));
}
The advantage of this template is that it's resilient to changes in example's constructor. If you add another argument, just call make_mytuple with the new arguments and it will work.
Your other point about the copying in the tuple construction is valid, but in reality I believe this will be optimal on most compilers. (a combination of RVO and elision of copies when passing an rvalue to a function by value).
You can use uniform initialization. Sadly, you cannot define a default value, argument will be initialized with the default constructor or 0.
#include <iostream>
#include <tuple>
enum class Result {Full, Partial, Empty};
std::tuple<bool, int, double> get_tuple(Result type)
{
if (type == Result::Full)
return {true, 42, 3.14159};
else if (type == Result::Partial)
return {true, 42, {}};
else
return {};
}
int main()
{
bool b;
int i;
double d;
std::tie(b, i, d) = get_tuple(Result::Full);
std::cout << b << " " << i << " " << d << std::endl;
std::tie(b, i, d) = get_tuple(Result::Partial);
std::cout << b << " " << i << " " << d << std::endl;
std::tie(b, i, d) = get_tuple(Result::Empty);
std::cout << b << " " << i << " " << d << std::endl;
return 0;
}
output:
1 42 3.14159
1 42 0
0 0 0