1

I wish get rid of boost dependency on my code. I have the following struct construct. When calling and using this struct at another place in the code boost::any_cast is used. I know a template class would do it, but finding it hard to write this template. - C++ Rookie.

 struct Properties {
 public:
 Properties() {}
 Properties(const std::string &s, const boost::any & p) {
      name = s;
      value = p;
 }

 template <typename T>
 Properties(T n) {
      value = n;
 }
 boost::any value;

 std::string name;
};
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • The program uses a variable: std::vector prop; and another instance of BOOST usage is boost::any_cast(prop[i]->value); – user3204803 Jan 17 '14 at 02:46

2 Answers2

5

Just for fun, I thought I'd create a minimalist any implementation:

//////////////////////////////////////////
// my_any.hpp
#include <memory>
#include <stdexcept>

struct my_any
{
    my_any() = default;
    template <typename T> my_any(T const& v) : _storage(new storage<T>(v)) { }
    my_any(my_any const& other)              : _storage(other._storage? std::move(other._storage->clone()) : nullptr) {}

    void swap(my_any& other)               { _storage.swap(other._storage); }
    friend void swap(my_any& a, my_any& b) { a.swap(b); };
    my_any& operator=(my_any other)        { swap(other); return *this; }

    // todo move semantics
private:
    struct storage_base { 
        virtual std::unique_ptr<storage_base> clone() = 0;
        virtual ~storage_base() = default; 
    };
    template <typename T>
    struct storage : storage_base {
        T value;
        explicit storage(T const& v) : value(v) {}
        std::unique_ptr<storage_base> clone() { return std::unique_ptr<storage_base>(new storage<T>(value)); }
    };
    std::unique_ptr<storage_base> _storage;
    template<typename T> friend T      & any_cast(my_any      &);
    template<typename T> friend T const& any_cast(my_any const&);
};

template <typename T> T& any_cast(my_any& a) { 
    if (auto p = dynamic_cast<my_any::storage<T>*>(a._storage.get()))
        return p->value;
    else
        throw std::bad_cast();
}

template <typename T> T const& any_cast(my_any const& a) { 
    if (auto p = dynamic_cast<my_any::storage<T> const*>(a._storage.get()))
        return p->value;
    else
        throw std::bad_cast();
}

You can then use it precisely the same fashion as your use-cases showed:

struct Properties {
    public:
        Properties(const std::string &s="", const my_any& p={}) 
            : name(s), value(p) {}

        template <typename T> Properties(T n) { value = n; }

        std::string name;
        my_any value;
};

#include <vector>
#include <iostream>

typedef std::vector<Properties> Props;

int main()
{
    Props v;
    v.emplace_back("bye", 42);
    v.emplace_back("vector", v);

    std::cout << "v.size(): "          << v.size()                           << "\n";
    std::cout << "v[0].value: "        << any_cast<int>(v[0].value)          << "\n";
    std::cout << "v[1].value.size(): " << any_cast<Props>(v[1].value).size() << "\n";

    v[0].value = v;

    try {
        std::cout << "v[0].value: " << any_cast<int>(v[0].value) << "\n";
    } catch(std::exception const& e)
    {
        std::cout << e.what() << " exception caught, ok!\n";
    }

    std::cout << "v[0].value.size(): " << any_cast<Props>(v[0].value).size() << "\n";
}

See the output Live On Coliru

sehe
  • 374,641
  • 47
  • 450
  • 633
  • error: 'virtual my_any::storage_base::~storage_base()' declared virtual cannot be defaulted in the class body Also, on the line: "Properties(const std::string &s="", const my_any& p={}) : name(s), value(p) {}" I get error: expected primary-expression before '{' token – user3204803 Jan 20 '14 at 18:58
  • Oh well. You didn't name a compiler... :| – sehe Jan 20 '14 at 19:02
  • So here goes: **[MSVC12 ok](http://rextester.com/CVHWF11127)**, [GCC 4.8 ok](http://coliru.stacked-crooked.com/a/b3c84eac8fb77493), [Clang 3.5 ok](http://coliru.stacked-crooked.com/a/69b25ed4f8b5a946). *AH*. You must be using [gcc 4.6](http://coliru.stacked-crooked.com/a/a8ce7f08ccc0ff9b). Well the fix is obvious :) – sehe Jan 20 '14 at 19:15
  • g++4.6.3 . removed "= default" added #include for bad_cast and used Properties(const std::string &s, const myany &p) { name = s; value = p; }.....Everything works. Can't rep up..since I need 15 rep to vote up. Many thanks. – user3204803 Jan 20 '14 at 19:54
  • @user3204803 You can always accept the answer that helped you most in the end. http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work (no need to accept mine, of course) – sehe Jan 20 '14 at 20:08
  • Is it possible to get this on older version of C++. My stuff still uses gcc 4.4.3. I've tried enabling C++11, but nullptr isn't supported. I followed www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf but still got " no match for ternary ‘operator?:’" – user3204803 Jan 23 '14 at 00:36
  • @user3204803 Does this help http://coliru.stacked-crooked.com/a/5585ef6c9a33ab35 ? I don't have a 4.4 compiler available. You _could_ of course do replace `unique_ptr` with `boost::scoped_ptr` or a raw pointer + rule of three/five... That's tedioius but not very complicated – sehe Jan 23 '14 at 08:30
1

boost::any uses type erasure to store objects of any type, and you can assign it values of different types at runtime. The any_cast is used to retrieve the original value, with the correct type, that was stored in the any object. For instance, your current class allows you to do this

Properties p("int", 42);
std::cout << boost::any_cast<int>(p.value) << '\n';
p = Properties("string", std::string("hello"));
std::cout << boost::any_cast<std::string>(p.value) << '\n';

You cannot just convert the class above into a template and get identical functionality. If you do that, you'll only be able to store a single type of value. And you must change the entire struct into a template, not just the constructor.

template<typename T>
struct Properties {
 public:
 Properties() {}
 Properties(std::string s, T p)
 : name(std::move(s))   // should use initialization list instead
 , value(std::move(p))  // of assignment within the body
 {}

 Properties(T n)
 : value(std::move(n))
 {}

 std::string name;
 T value;
};

However, the code I posted above is now illegal.

Properties<int> p("int", 42);
std::cout << p.value << '\n';
// p = Properties<std::string>("string", std::string("hello"));
// will not compile because Properties<int> and Properties<std::string> are
// distinct types 

If these restrictions are OK, then the modified definition should work for you.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Thanks for the reply. But the restriction are not OKay. The program uses a variable: std::vector prop; and another instance of BOOST usage is boost::any_cast(prop[i]->value); – user3204803 Jan 17 '14 at 02:46
  • 2
    @user3204803, then I'm afraid you'll have to stick to `boost::any` or roll your own. – Shoe Jan 17 '14 at 08:42
  • @user3204803 I'm afraid Jefffrey is right; rolling your own is pretty much the only other option in that case. Thankfully, `boost::any` is relatively simple to implement, [here's](http://www.boost.org/doc/libs/1_55_0/boost/any.hpp) the source code (it'll get a lot shorter than that if you get rid of the various compiler workarounds and other options) – Praetorian Jan 17 '14 at 18:22