I am trying to use the boost::variant with template types. For example, I have a template type Tag<T> and the boost::variant AnyTag comprises types such as Tag<double>, Tag<int> and Tag<std::string>. Each Tag<T> has members of type T.
Now, I would like to put those variants in a container and simply assign values during runtime, e.g.,
for(AnyTag & tag: AllTags) {
setValue(tag, getValueFromXml());
}
The function setValue(AnyTag &tag, T &val) must use the runtime type of the AnyTag tag in order to correctly assign the tag with the correct value.
My attempt to solving the problem can be found below and it makes use of another variant which included only the possible T types that could be used in the AnyTag (TagValueType).
template<typename T, typename = void>
class Tag {};
template <typename T>
class Tag<T, EnableIf<std::is_arithmetic<T>>> {
public:
T value = 0;
std::string address = "";
T maxValue = std::numeric_limits<T>::max();
typedef T value_type;
};
template <typename T>
class Tag<T, DisableIf<std::is_arithmetic<T>>> {
public:
T value;
std::string address = "";
typedef T value_type;
};
typedef boost::variant<Tag<std::string>,
Tag<double>,
Tag<int>,
> AnyTag;
typedef boost::variant<std::string, double, int> TagValueType;
class tag_set_value_visitor: public boost::static_visitor<void>
{
const TagValueType & value;
public:
tag_set_value_visitor(const TagValueType & val): value(val){}
template <typename T>
void operator()(T & tag) const
{
tag.value = boost::get<typename T::value_type>(value);
}
};
inline void setValue(AnyTag & tag, const TagValueType & val) {
assert(tag.which() == val.which());
boost::apply_visitor( tag_set_value_visitor(val), tag );
}
Unfortunately, this approach is not what I would like because for example during compilation there is not problem if I do the following:
AnyTag a = Tag<int>();
setValue(a, double(1.3));
but during runtime, the boost library detects the type mismatch and crashes the program.
So, my solution is kind of a type erasure that just postpones the problem.
What I would like to have is a setValue(AnyTag &tag, T &val) where T is the runtime type of the AnyTag.
I get that that's what the variant's visitor tries to do, but there is a problem in this case because when we construct the visitor we must know the type that we are going to use.
Any ideas or any thoughts about this problem?
P.S.: Sorry for the long post but I couldn't find a way to explain my thought process with fewer words.
Use¹ a binary visitor.
Implement the operator() to do nothing except for corresponding types.
Handle mismatches to taste (I return a boolean indicating success):
Live On Coliru
#include <boost/any.hpp>
#include <boost/variant.hpp>
#include <boost/mpl/vector.hpp>
#include <string>
using namespace boost;
template <typename T>
struct Tag {
T value;
};
using Types = mpl::vector<std::string, double, int>;
using Tags = mpl::transform<Types, Tag<mpl::_1> >::type;
using Variant = make_variant_over<Types>::type;
using AnyTag = make_variant_over<Tags>::type;
namespace mydetail {
struct assign_to : boost::static_visitor<bool> {
template <typename V> bool operator()(Tag<V>& tagged, V const& value) const {
tagged.value = value;
return true;
}
template <typename T, typename V> bool operator()(T&&, V&&) const {
return false;
}
};
}
bool setValue(AnyTag &tag, Variant const& val) {
return boost::apply_visitor(mydetail::assign_to(), tag, val);
}
int main() {
AnyTag t;
t = Tag<std::string>();
// corresponding type assigns and returns true:
assert(setValue(t, "yes works"));
// mismatch: no effect and returns false:
assert(!setValue(t, 42));
assert(!setValue(t, 3.1415926));
}
¹ If I understood your goal correctly. I've focused on the "What I would like to have is a setValue(AnyTag &tag, T &val) where T is the runtime type of the AnyTag." part of the request.
Related
I have begun a project that makes heavy use of c++20 concepts as a way of learning some of the new c++20 features. As part of it, I have a function template that takes a single argument and operates on it. I wish to have the flexibility to pass types to this function that specify another type that they operate on, but that defaults to something else if that specification doesn't exist.
For example, here is a type that specifies a type that it operates on:
struct has_typedef_t
{
typedef int my_type; //operates on this type
void use_data(const my_type& data) const
{
//do something else
}
};
and one that does not specify a type, but operates on a default type specified elsewhere:
typedef std::string default_type;
struct has_no_typedef_t
{
void use_data(const default_type& data) const
{
//do something
}
};
I have a simple concept that can tell me if any given type has this specification:
template <class T> concept has_type = requires(T t) {typename T::my_type;};
And the function might look something like the following:
template <class T> void my_function(const T& t)
{
//Here I want a default-constructed value of the default
//type if the argument doesn't have a typedef
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
t.use_data(input_data);
}
The problem here is that the second template argument is invalid for anything that doesn't specify a type, e.g. has_no_typedef_t. An example program:
int main(int argc, char** argv)
{
has_no_typedef_t s1;
has_typedef_t s2;
// my_function(s1); // doesn't compile:
//'has_no_typedef_t has no type named 'my_type'
my_function(s2); //compiles
return 0;
}
What I am looking for is a replacement for the following line:
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
as this is causing the problem. I am aware that I can pull of some tricks using an overloaded function using the concept above and combining decltype and declval, but I am looking for something clean and have not been able to come up with anything. How can I achieve this behavior cleanly?
Below is the full code for the full picture (c++20):
#include <iostream>
#include <type_traits>
#include <concepts>
#include <string>
template <class T> concept has_type = requires(T t) {typename T::my_type;};
struct has_typedef_t
{
typedef int my_type;
void use_data(const my_type& data) const
{
//do something
}
};
typedef std::string default_type;
struct has_no_typedef_t
{
void use_data(const default_type& data) const
{
//do something else
}
};
template <class T> void my_function(const T& t)
{
//Here I want a default-constructed value of the default
//type if the argument doesn't have a typedef
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
t.use_data(input_data);
}
int main(int argc, char** argv)
{
has_no_typedef_t s1;
has_typedef_t s2;
// my_function(s1); // doesn't compile:
//'has_no_typedef_t has no type named 'my_type'
my_function(s2); //compiles
return 0;
}
This has had a solution since C++98: a traits class. Concepts just makes it a bit easier to implement:
template<typename T>
struct traits
{
using type = default_type;
};
template<has_type T>
struct traits<T>
{
using type = T::my_type;
};
Without concepts, you'd need to use SFINAE to turn on/off the specializations based on whether the type has the trait or not.
You can use lambda combined with if constexpr to determine the type of the return. The type_identity here is to solve the problem that the type may not be default_initializable.
typename decltype([]{
if constexpr (requires { typename T::my_type; })
return std::type_identity<typename T::my_type>{};
else
return std::type_identity<default_type>{};
}())::type input_data;
t.use_data(input_data);
Demo
The standard says the following about specializing templates from the standard library (via What can and can't I specialize in the std namespace? )
A program may add a template
specialization for any standard library template to namespace std only
if the declaration depends on a user-defined type and the
specialization meets the standard library requirements for the
original template and is not explicitly prohibited.
Is it legal to specialize standard library templates with a standard library class specialized with a user defined class?
For example, specializing std::hash for std::shared_ptr<MyType>?
From reading the above paragraph and linked question, it sounds like it should be, as the declaration of the specialization is dependent on MyType, however "Unless explicitly prohibited" worries me slightly.
The example below compiles and works as expected (AppleClang 7.3), but is it legal?
#include <unordered_set>
#include <memory>
#include <cassert>
#include <string>
struct MyType {
MyType(std::string id) : id(id) {}
std::string id;
};
namespace std {
template<>
struct hash<shared_ptr<MyType>> {
size_t operator()(shared_ptr<MyType> const& mine) const {
return hash<string>()(mine->id);
}
};
template<>
struct equal_to<shared_ptr<MyType>> {
bool operator()(shared_ptr<MyType> const& lhs, shared_ptr<MyType> const& rhs ) const {
return lhs->id == rhs->id;
}
};
}
int main() {
std::unordered_set<std::shared_ptr<MyType>> mySet;
auto resultA = mySet.emplace(std::make_shared<MyType>("A"));
auto resultB = mySet.emplace(std::make_shared<MyType>("B"));
auto resultA2 = mySet.emplace(std::make_shared<MyType>("A"));
assert(resultA.second);
assert(resultB.second);
assert(!resultA2.second);
}
Yes, that is legal.
It is even questionably legal to specialize for std::shared_ptr<int> at one point; I don't know if they patched that ambiguity in the standard as a defect or not.
Note that that is a poor implemenation of a hash for global use. First, because it doesn't support null shared pointers. Second, because hashing a shared pointer as always the int value is questionable. It is even dangerous, because if a shared pointer to an int in a container has that int change, you just broke the program.
Consider making your own hasher for these kind of cases.
namespace notstd {
template<class T, class=void>
struct hasher_impl:std::hash<T>{};
namespace adl_helper {
template<class T>
std::size_t hash( T const& t, ... ) {
return ::notstd::hasher_impl<T>{}(t);
}
};
namespace adl_helper2 {
template<class T>
std::size_t hash_helper(T const& t) {
using ::notstd::adl_helper::hash;
return hash(t);
}
}
template<class T>
std::size_t hash(T const& t) {
return ::notstd::adl_helper2::hash_helper(t);
}
struct hasher {
template<class T>
std::size_t operator()(T const& t)const {
return hash(t);
}
};
}
Now this permits 3 points of customization.
First, if you override std::size_t hash(T const&) in the namespace containing T, it picks it up.
Failing that, if you specialize notstd::hasher_impl<T, void> for your type T, it picks it up.
Third, if both of those fail, it invokes std::hash<T>, picking up any specializations.
Then you can do:
std::unordered_set<std::shared_ptr<MyType>, ::notstd::hasher> mySet;
and add:
struct MyType {
MyType(std::string id) : id(id) {}
std::string id;
friend std::size_t hash( MyType const& self) {
return ::notstd::hash(self.id);
}
friend std::size_t hash( std::shared_ptr<MyType> const& self) {
if (!self) return 0;
return ::notstd::hash(*self);
}
};
which should give you a smart hash on on shared_ptr<MyType>.
This keeps the danger that someone changes id on a shared_ptr<MyType> which breaks every container containing the shared_ptr<MyType> in a non-local manner.
Shared state is the devil; consider writing a copy on write pointer if you are really worried about copying these things being expensive.
I'm trying to build a small C++ example using boost fusion. However, Visual Studio 2013 gives me build errors for the following piece of code. It should simply go over a associative struct and print all member names to the console:
#include <iostream>
#include <type_traits>
#include <boost/fusion/adapted/struct/define_assoc_struct.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/transformation/zip.hpp>
#include <boost/fusion/algorithm/transformation/transform.hpp>
namespace keys
{
struct name
{};
struct id
{};
}
BOOST_FUSION_DEFINE_ASSOC_STRUCT((), Student,
(std::string, name, keys::name)
(int, id, keys::id)
);
struct getnames
{
template<typename Sig>
struct result;
template <typename S, typename T>
struct result<getnames(S, T)>
{
typedef std::string type;
};
template<class Struct, class N>
typename result<getnames(Struct, N)>::type operator() (const N& i) const
{
return boost::fusion::extension::struct_member_name<Struct, i>::call();
}
};
struct print
{
template<typename Sig>
struct result;
template <typename T>
struct result<print(T)>
{
typedef void type;
};
template<class S>
void operator() (const S& i) const
{
std::cout << i << std::endl;
};
};
int main()
{
Student j = {"John", 42};
auto names = boost::fusion::transform(j, getnames());
boost::fusion::for_each(names, print());
return 0;
}
This is my error:
boost/fusion/view/transform_view/detail/deref_impl.hpp(38): error C2039: 'type' : is not a member of 'boost::mpl::apply<boost::fusion::detail::apply_transform_result<getnames>,const std::basic_string<char,std::char_traits<char>,std::allocator<char>> &,boost::mpl::na,boost::mpl::na,boost::mpl::na,boost::mpl::na>'
and four more errors which are coming up because of the first one.
To be honest, I am not an expert in the usage of boost fusion so maybe I simply missed something important here and someone else can help me.
There are several problems with your code.
1) In the Functor getnames, the signature of the result type and the signature of operator() are inconsistent (one takes one argument, the other takes two).
2) in the operator()(const N& i), i is a runtime variable. It cannot appear as a template parameter in the expression boost::fusion::extension::struct_member_name<Struct, i>.
I am not sure how to help without knowing what you want to do with getnames. Try to get to a consistent code first.
I give up, please help explain this behaviour. The example I present below is the simplest one I could think of, but it sums up the problem (using g++ 4.9.2 on Cygwin with c++14 enabled). I want to create a class which will behave similar to std::mem_fn. Here is my class:
template <class R, class T, R(T::*P)() const >
struct property {
static R get(const T& t) {
return (t.*P)();
}
};
where R is the return type and T is the type of the object I am interesting in. The third template parameter is a pointer to member function. So far, so good.
I then create a simple class which holds an integer as follows
class data_class {
public:
unsigned get_data() const {
return m_data;
}
private:
unsigned m_data;
};
This is the class which will be used in the property class shown before.
Now I create two classes which inherit from data_class as follows
struct my_classA
: public data_class {
using data = property<unsigned, data_class, &data_class::get_data>;
};
//same as my_classA, only templated
template <int I>
struct my_classB
: public data_class {
using data = property<unsigned, data_class, &data_class::get_data>;
};
They have the exact same inner typedef, but my_classB is templated. Now the following types should in theory be the same:
using target_t = property<unsigned, data_class, &data_class::get_data>;
using test1_t = typename my_classA::data;
using test2_t = typename my_classB<1>::data;
However my compiler says that only test1_t and target_t are the same. The type deduced for test2_t is apparently
property<unsigned int, data_class, (& data_class::get_data)> >
where this type has these brackets around the pointer to member function. Why test2_t is not the same as target_t? Here is the full code in case you want to try it on your system. Any help is much appreciated.
#include <type_traits>
class data_class {
public:
unsigned get_data() const {
return m_data;
}
private:
unsigned m_data;
};
//takes return type, class type, and a pointer to member function
//the get function takes an object as argument and uses the above pointer to call the member function
template <class R, class T, R(T::*P)() const >
struct property {
static R get(const T& t) {
return (t.*P)();
}
};
struct my_classA
: public data_class {
using data = property<unsigned, data_class, &data_class::get_data>;
};
//same as my_classA, only templated
template <int I>
struct my_classB
: public data_class {
using data = property<unsigned, data_class, &data_class::get_data>;
};
//used to produce informative errors
template <class T>
struct what_is;
//all 3 types below should, in theory, be the same
//but g++ says that test2_t is different
using target_t = property<unsigned, data_class, &data_class::get_data>;
using test1_t = typename my_classA::data;
using test2_t = typename my_classB<1>::data;
static_assert(std::is_same<target_t, test1_t>::value, ""); //this passes
static_assert(std::is_same<target_t, test2_t>::value, ""); //this does not
int main() {
what_is<test1_t> t1;
what_is<test2_t> t2;
}
I ran your code with c++11 because I'm not very familiar with c++14 yet. But all I replaced were the using (aliases) with typedefs and simplified the code a little bit. Nothing to affect its output.
I got the desired results by adding a typename T to the inherited classB template which when instantiated, it will replace the R with T, so in this case "unsigned".
#include <iostream>
#include <type_traits>
template <typename R, typename T, R(T::*P)() const>
struct property
{
static R get(const T& t)
{
return (t.*P)();
}
};
struct data_class
{
private:
unsigned m_data;
public:
unsigned get_data() const
{
return m_data;
}
};
struct my_classA : public data_class
{
typedef property<unsigned, data_class, &data_class::get_data> data;
};
template <typename T, int>
struct my_classB : public data_class
{
typedef property<T, data_class, &data_class::get_data> data;
};
int main()
{
typedef typename my_classA::data normClassA;
typedef typename my_classB<unsigned,1>::data tmplClassB;
std::cout<< std::is_same< property<unsigned, data_class, &data_class::get_data> , normClassA >::value <<std::endl;
std::cout<< std::is_same< property<unsigned, data_class, &data_class::get_data> , tmplClassB >::value <<std::endl;
}
The result is this:
~$g++ -std=c++11 test.cpp
~$./a.out
1
1
I think the problem has to do with the class template instantiation criteria because when I originally tried to print the sizeof's of the two classes, my_classA::data returned 1, but my_classB<1>::data ended in a compiller error. I'm still quite fuzzy as to why this occurs. Technically it should have instantiated the class template just fine. Maybe it's the property inside the classB template that was falsely instantiated. I'll look more into this but if you find the answer, please post it. It's an interesting one!
EDIT:
The original code works fine on Cygwin GCC 4.8.2. Result is 1 and 1. Maybe it's just a gcc4.9.2 compiler issue.
Note: I'm aware of boost::variant, but I am curious about the design principles. This question mostly for self-education.
Original post
At my current job I found an old variant class implementation. It's implemented with a union and can only support a handful of datatypes. I've been thinking about how one should go about designing an improved version. After some tinkering I ended up with something that seems to work. However I'd like to know your opinion about it. Here it is:
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <boost/shared_ptr.hpp>
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<T>(inValue)),
mClassName(typeid(T).name())
{
}
template<class T>
T getValue() const
{
if (typeid(T).name() != mClassName)
{
throw std::logic_error("Non-matching types!");
}
return dynamic_cast<VariantImpl<T>*>(mImpl.get())->getValue();
}
template<class T>
void setValue(T inValue)
{
mImpl.reset(new VariantImpl<T>(inValue));
mClassName = typeid(T).name();
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T getValue() const { return mValue; }
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
std::string mClassName;
};
int main()
{
// Store int
Variant v(10);
int a = 0;
a = v.getValue<int>();
std::cout << "a = " << a << std::endl;
// Store float
v.setValue<float>(12.34);
float d = v.getValue<float>();
std::cout << "d = " << d << std::endl;
// Store map<string, string>
typedef std::map<std::string, std::string> Mapping;
Mapping m;
m["one"] = "uno";
m["two"] = "due";
m["three"] = "tre";
v.setValue<Mapping>(m);
Mapping m2 = v.getValue<Mapping>();
std::cout << "m2[\"one\"] = " << m2["one"] << std::endl;
return 0;
}
Output is correct:
a = 10
d = 12.34
m2["one"] = uno
My SO questions are:
Is this implementation correct?
Will the dynamic cast in getValue() work as expected (I'm not certain)
Should I return T as a const reference instead? Or can I count on return-value-optimization to kick in?
Any other problems or suggestions?
Update
Thanks to #templatetypedef for his suggestions. This updated version only uses dynamic_cast to check if the types match. Type mismatches caused by differences in constness are now avoided thanks to the TypeWrapper classes (which I have shamelessly stolen from the Poco C++ project).
So this is the current version. It's likely to contain a few errors though, as I'm not familiar with the idea of modifying const/ref on template templates. I'll have a fresh look tomorrow.
template <typename T>
struct TypeWrapper
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue))
{
}
template<class T>
typename TypeWrapper<T>::REFTYPE getValue()
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
typename TypeWrapper<T>::CONSTREFTYPE getValue() const
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
void setValue(typename TypeWrapper<T>::CONSTREFTYPE inValue)
{
mImpl.reset(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue));
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
};
This implementation is close to correct, but it looks like it has a few bugs. For example, this code:
if (typeid(T).name() != mClassName)
is not guaranteed to work correctly because the .name() function in type_info is not guaranteed to return a unique value for each type. If you want to check if the types match, you should probably use something like this:
if (typeid(*mImpl) == typeid(VariantImpl<T>))
Which more accurately checks if the type matches. Of course, you need to watch out for const issues, since storing a const T and storing a T will yield different types.
As for your question about dynamic_cast, in the case you've described you don't need to use the dynamic_cast because you already have a check to confirm that the type will match. Instead, you can just use a static_cast, since you've already caught the case where you have the wrong type.
More importantly, though, what you've defined here is an "unrestricted variant" that can hold absolutely anything, not just a small set of restricted types (which is what you'd normally find in a variant). While I really like this code, I'd suggest instead using something like Boost.Any or Boost.Variant, which has been extensively debugged and tested. That said, congrats on figuring out the key trick that makes this work!
At the risk of providing a non-answer, since you are already using Boost, I recommend you try Boost.Variant or Boost.Any instead of rolling your own implementation.
Better to use std::auto_ptr, as there's no reference counting semantics required. I would normally return by reference as it's perfectly legal to change the value within, or by pointer to allow NULL.
You should use dynamic_cast to match types, not typeid(), and you could just use Boost. typeid() seems like it should provide this but in reality it doesn't because of the open-endedness of it's specification, whereas dynamic_cast is always exactly and unambiguously correct.