Click here to Skip to main content
Click here to Skip to main content

High Performance Dynamic Typing in C++ using a Replacement for boost::any

By , 9 May 2011
Rate this:
Please Sign up or sign in to vote.

Introduction

The Boost library provides a very useful little class called boost::any which can contain a value of virtually any type as long as that value supports copy construction and assignment. This boost::any type allows dynamic querying of the contained type, and safe type conversions. Despite its usefulness, boost::any is not as efficient as it could be, so I have rewritten my own, which I have immodestly called cdiggins::any.

Updated in 2011: After five years, I have rewritten the class from scratch to fix some really nasty bugs and to simplify the code.

Using cdiggins::any

The cdiggins::any type can be used to hold normal value types, and provides a mechanism to safely and explicitly cast back to the appropriate type.

any a = 42;
cout << a.cast<int>() << endl;
a = 13;
cout << a.cast<int>() << endl;
a = "hello";
cout << a.cast<const char*>() << endl;
a = std::string("1234567890");
cout << a.cast<std::string>() << endl;
int n = 42;
a = &n;
cout << *a.cast<int*>() << endl;
any b = true;
cout << b.cast<bool>() << endl;
swap(a, b);        
cout << a.cast<bool>() << endl;
a.cast<bool>() = false;
cout << a.cast<bool>() << endl;

Design of cdiggins::any

The cdiggins::any class contains two pointers: one to a policy class (any::policy) and one is a pointer to the data (any::data) which may in certain cases be used to contain the data itself (for example, if storing a primitive data type smaller than or equal to the size of a pointer).

The policy class is used for performing allocations, deallocation, copies, etc., and determines whether to use any::data to hold the data or point to the data.

Finally, without further ado, here is the implementation of the cdiggins::any class for your enjoyment:

#pragma once
/*
 * (C) Copyright Christopher Diggins 2005-2011
 * (C) Copyright Pablo Aguilar 2005
 * (C) Copyright Kevlin Henney 2001
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt
 */

#include <stdexcept>

namespace cdiggins
{
namespace anyimpl
{
    struct bad_any_cast 
    {
    };

    struct empty_any 
    {
    };

    struct base_any_policy 
    {
        virtual void static_delete(void** x) = 0;
        virtual void copy_from_value(void const* src, void** dest) = 0;
        virtual void clone(void* const* src, void** dest) = 0;
        virtual void move(void* const* src, void** dest) = 0;
        virtual void* get_value(void** src) = 0;
        virtual size_t get_size() = 0;
    };

    template<typename T>
    struct typed_base_any_policy : base_any_policy
    {
        virtual size_t get_size() { return sizeof(T); } 
    };

    template<typename T>
    struct small_any_policy : typed_base_any_policy<T>
    {
        virtual void static_delete(void** x) { }
        virtual void copy_from_value(void const* src, void** dest)
            { new(dest) T(*reinterpret_cast<T const*>(src)); }
        virtual void clone(void* const* src, void** dest) { *dest = *src; }
        virtual void move(void* const* src, void** dest) { *dest = *src; }
        virtual void* get_value(void** src) { return reinterpret_cast<void*>(src); }
    };

    template<typename T>
    struct big_any_policy : typed_base_any_policy<T>
    {
        virtual void static_delete(void** x) { if (*x) 
            delete(*reinterpret_cast<T**>(x)); *x = NULL; }
        virtual void copy_from_value(void const* src, void** dest) { 
           *dest = new T(*reinterpret_cast<T const*>(src)); }
        virtual void clone(void* const* src, void** dest) { 
           *dest = new T(**reinterpret_cast<T* const*>(src)); }
        virtual void move(void* const* src, void** dest) { 
          (*reinterpret_cast<T**>(dest))->~T(); 
          **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src); }
        virtual void* get_value(void** src) { return *src; }
    };

    template<typename T>
    struct choose_policy 
    {
        typedef big_any_policy<T> type;
    };

    template<typename T> 
    struct choose_policy<T*> 
    { 
        typedef small_any_policy<T*> type; 
    };
    
    struct any;

    /// Choosing the policy for an any type is illegal, but should never happen.
    /// This is designed to throw a compiler error.
    template<>
    struct choose_policy<any>
    {
        typedef void type;
    };

    /// Specializations for small types.
    #define SMALL_POLICY(TYPE) template<> struct 
       choose_policy<TYPE> { typedef small_any_policy<TYPE> type; };

    SMALL_POLICY(signed char);
    SMALL_POLICY(unsigned char);
    SMALL_POLICY(signed short);
    SMALL_POLICY(unsigned short);
    SMALL_POLICY(signed int);
    SMALL_POLICY(unsigned int);
    SMALL_POLICY(signed long);
    SMALL_POLICY(unsigned long);
    SMALL_POLICY(float);
    SMALL_POLICY(bool);

    #undef SMALL_POLICY

    /// This function will return a different policy for each type. 
    template<typename T>
    base_any_policy* get_policy()
    {
        static typename choose_policy<T>::type policy;
        return &policy;
    };
}

struct any
{
private:
    // fields
    anyimpl::base_any_policy* policy;
    void* object;

public:
    /// Initializing constructor.
    template <typename T>
    any(const T& x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    {
        assign(x);
    }       

    /// Empty constructor. 
    any() 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    { }

    /// Special initializing constructor for string literals. 
    any(const char* x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) 
    { 
        assign(x);
    }

    /// Copy constructor. 
    any(const any& x) 
        : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL)         
    {            
        assign(x);
    }

    /// Destructor. 
    ~any() {
        policy->static_delete(&object);
    }

    /// Assignment function from another any. 
    any& assign(const any& x) {
        reset();
        policy = x.policy;
        policy->clone(&x.object, &object);
        return *this;
    }

    /// Assignment function. 
    template <typename T>
    any& assign(const T& x) {
        reset();
        policy = anyimpl::get_policy<T>();
        policy->copy_from_value(&x, &object);
        return *this;
    }

    /// Assignment operator.
    template<typename T>
    any& operator=(const T& x) {
        return assign(x);
    }

    /// Assignment operator, specialed for literal strings. 
    /// They have types like const char [6] which don't work as expected. 
    any& operator=(const char* x) {
        return assign(x);
    }

    /// Utility functions
    any& swap(any& x) {
        std::swap(policy, x.policy);
        std::swap(object, x.object);
        return *this;
    }

    /// Cast operator. You can only cast to the original type.
    template<typename T>
    T& cast() {
        if (policy != anyimpl::get_policy<T>()) 
            throw anyimpl::bad_any_cast();
        T* r = reinterpret_cast<T*>(policy->get_value(&object)); 
        return *r;
    }

    /// Returns true if the any contains no value. 
    bool empty() const {
        return policy == anyimpl::get_policy<anyimpl::empty_any>();
    }

    /// Frees any allocated memory, and sets the value to NULL.
    void reset() {
        policy->static_delete(&object);
        policy = anyimpl::get_policy<anyimpl::empty_any>();
    }

    /// Returns true if the two types are the same. 
    bool compatible(const any& x) const {
        return policy == x.policy;
    }
};
}

Final Words

Thanks to Pablo Aguilar who helped with an early version of the cdiggins::any class. Thanks to Raute who helped me identify the critical problems in the original version of cdiggins::any.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Christopher Diggins
Software Developer Autodesk
Canada Canada
This article was written by Christopher Diggins, a computer science nerd who currently works at Autodesk as an SDK specialist.
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
Questionnot working with "unsigned int" Pinmembercristi.arghiroiu19-Feb-14 1:00 
Questionfix the const problem [modified] Pinmemberpj220_20068-Aug-13 22:12 
AnswerRe: fix the const problem Pinmemberskyformat99@gmail.com31-Jan-14 21:47 
AnswerRe: fix the const problem PinmemberDavid O'Neil28-Feb-14 20:23 
QuestionBugfix [modified] PinmemberDavid O'Neil9-Jul-13 20:45 
It has been a while, but I finally got around to looking at the issue more deeply. The problem I mentioned in my previous message only cropped up in semi-complicated circumstances, and with constructs evidently not used by anyone else who has used your class. The base problem appears to be that your empty constructor, although it defaults to empty_any, under the hood is defaulting the the big_any_policy which is calling new and delete. That becomes a big problem when the any object changes to holding a different type via copy construction, or operator=().
 
That issue came up intermittently in my code - sometimes I didn't get an error, other times I did. The trigger seemed to be that often I used 'any' objects in classes which were held in vectors, or other containers, and a lot of copy construction was occurring.
 
Therefore I had to modify your empty constructors to default to signed int construction, to make them default to small_any_policy. I also modified copy constructors, and created a couple others in order to address all of my usage cases. Another change I just saw is in the copy_from_value routine for the small_any_policy, as commented below. The following isn't guaranteed to be bug-free, but using it no longer results in undefined behavior bug crashes in my program. Feel free to modify it back into your original class, as I've wrapped it in my own namespace for typing convenience on my end.
 
If you, or anyone sees anything I've missed, please holler. And thank you for your work on this, and making it freely available! It taught me a lot, and is used extensively in my code.
 
David
 
//-------------------------------------------------------------------------------------------
// The following is derived from Christopher Diggins, Pablo Aguilar, and Kevlin Henney's
// 'any' work available at:
// http://www.codeproject.com/Articles/11250/High-Performance-Dynamic-Typing-in-C-using-a-Repla.
//
// Their original copyright notice is as follows:
// * (C) Copyright Christopher Diggins 2005
// * (C) Copyright Pablo Aguilar 2005
// * (C) Copyright Kevlin Henney 2001
// *
// * Distributed under the Boost Software License, Version 1.0. (See
// * accompanying file LICENSE_1_0.txt or copy at
// * http://www.boost.org/LICENSE_1_0.txt)
//
// The only three changes I have made is 1) to toss it into the 'DWL' namespace (as I
// don't like typing more than I have to, and those extra four letters add up, especially
// as I use it in function signatures), 2) replacement of the 'bad_any_cast' derived from
// std::bad_cast with a DwlException, and 3) added the templated 'isA' function to
// make testing for type much easier.  Of course, the seccond change makes 'catching'
// just this type of error much harder if you wish to do so, but you may change to your
// heart's content for your own projects since you have the code.
// Thanks Christopher, Pablo, and Kevlin for making this available.
//
// Oops - I forgot about the changes listed above in my preface to this code.
//-------------------------------------------------------------------------------------------

#ifndef RMW_CDIGGINS_ANY_HPP
#define RMW_CDIGGINS_ANY_HPP
 

//#pragma once
/*
 * (C) Copyright Christopher Diggins 2005-2011
 * (C) Copyright Pablo Aguilar 2005
 * (C) Copyright Kevlin Henney 2001
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt
 */
 
#include <stdexcept>

 
//namespace cdiggins
namespace dwl {
   namespace anyimpl {
 
      struct empty_any {
         };
 
      struct base_any_policy {
      
         virtual void static_delete(void** x) = 0;
         
         virtual void copy_from_value(void const* src, void** dest) = 0;
         
         virtual void clone(void* const* src, void** dest) = 0;
         
         virtual void move(void* const* src, void** dest) = 0;
         
         virtual void* get_value(void** src) = 0;
         
         virtual size_t get_size() = 0;
         
         };
 
      template<typename T>
      struct typed_base_any_policy : base_any_policy {
         virtual size_t get_size() { return sizeof(T); } 
         };
 
      template<typename T>
      struct small_any_policy : typed_base_any_policy<T> {
      
         virtual void static_delete(void** x) { }
         
         virtual void copy_from_value(void const* src, void** dest) {
            //This used to be:
            //new(dest) T(*reinterpret_cast<T const*>(src)); 
            //But this is the small_any_policy, and ints and other small objects
            //shouldn't need to be pointed to by an object.
            //In this case, we are really receiving the address of the the 'object' variable
             *dest = *(reinterpret_cast<void**>((const_cast<void*>(src))));
            }
            
         virtual void clone(void* const* src, void** dest) { *dest = *src; }
         
         virtual void move(void* const* src, void** dest) { *dest = *src; }
         
         virtual void* get_value(void** src) { return reinterpret_cast<void*>(src); }
         
         };
 
      template<typename T>
      struct big_any_policy : typed_base_any_policy<T> {
         virtual void static_delete(void** x) {
            if (*x) 
               delete(*reinterpret_cast<T**>(x));
            *x = NULL;
            }
            
         virtual void copy_from_value(void const* src, void** dest) { 
            *dest = new T(*reinterpret_cast<T const*>(src));
            }
            
         virtual void clone(void* const* src, void** dest) { 
            *dest = new T(**reinterpret_cast<T* const*>(src));
            }
            
         virtual void move(void* const* src, void** dest) { 
           (*reinterpret_cast<T**>(dest))->~T(); 
           **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
           }
           
         virtual void* get_value(void** src) { return *src; }
         
      };
 
      template<typename T>
      struct choose_policy {
         typedef big_any_policy<T> type;
         };
 
      template<typename T> 
      struct choose_policy<T*> { 
         typedef small_any_policy<T*> type; 
         };
      
      struct any; //Forward declaration

      /// Choosing the policy for an any type is illegal, but should never happen.
      /// This is designed to throw a compiler error.
      template<>
      struct choose_policy<any> {
         typedef void type;
         };
 
      /// Specializations for small types.
      #define SMALL_POLICY(TYPE) template<> struct choose_policy<TYPE> { typedef small_any_policy<TYPE> type; };
 
      SMALL_POLICY(signed char);
      SMALL_POLICY(unsigned char);
      SMALL_POLICY(signed short);
      SMALL_POLICY(unsigned short);
      SMALL_POLICY(signed int);
      SMALL_POLICY(unsigned int);
      SMALL_POLICY(signed long);
      SMALL_POLICY(unsigned long);
      SMALL_POLICY(float);
      SMALL_POLICY(bool);
 
      #undef SMALL_POLICY
 
      /// This function will return a different policy for each type. 
      template<typename T>
      base_any_policy* get_policy() {
         static typename choose_policy<T>::type policy;
         return &policy;
         };
                  
      }  //End of anyimpl namespace

 

      struct any {
         private:
            // fields
            anyimpl::base_any_policy* policy;
            void* object;
 
         public:
            /// Initializing constructor.
            template <typename T>
            //This used to be:
            //any(const T& x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) {
            //I am changing it to force the initial construction to use the small_policy:
            any(const T& x) : policy(anyimpl::get_policy<unsigned int>()), object(NULL) {
               assign(x);
               }      
 
            /// Empty constructor. 
            //This used to be:
            //any() : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) {
            //But why should an empty 'any' receive a default 'big_any_policy'???
            //This starts calling 'new' and 'delete' under the hood.
            //So let's try the following to force a small_policy:
            any() : policy(anyimpl::get_policy<unsigned int>()), object(NULL) {
               }
 
            /// Special initializing constructor for string literals. 
            any(const char* x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), 
                        object(NULL) {
                        
               assign(x);
               }
 
            /// Copy constructor. 
            any(const any& x) : policy(anyimpl::get_policy<anyimpl::empty_any>()), object(NULL) {
               assign(x);
               }
 
            /// Destructor. 
            ~any() {
               policy->static_delete(&object);
               }
 
            /// Assignment function from another any. 
            any& assign(const any& x) {
               reset();
               policy = x.policy;
               policy->clone(&x.object, &object);
               return *this;
               }
 
            /// Assignment function. 
            template <typename T>
            any& assign(const T& x) {
               reset();
               policy = anyimpl::get_policy<T>();
               policy->copy_from_value(&x, &object);
               return *this;
               }
 
            /// Assignment operator.
            template<typename T>
            any& operator=(const T& x) {
               return assign(x);
               }
 
            any& operator=(const any & x) {
               /*policy = x.policy;
               policy->copy_from_value(&x.object, &object);
               return *this;*/
               reset();
               return assign(x);
               }
               
            any& operator()(const any & x) {
               policy = x.policy;
               policy->copy_from_value(&x.object, &object);
               return *this;
               }
 
            /// Assignment operator, specialed for literal strings. 
            /// They have types like const char [6] which don't work as expected. 
            any& operator=(const char* x) {
               return assign(x);
               }
 
            /// Utility functions
            any& swap(any& x) {
               std::swap(policy, x.policy);
               std::swap(object, x.object);
               return *this;
               }
               
            /// Cast operator. You can only cast to the original type.
            template<typename T>
            T& cast() {
               if (policy != anyimpl::get_policy<T>()) 
                  throw DwlException(_T("Bad 'any' cast"));// was throw anyimpl::bad_any_cast();
               T* r = reinterpret_cast<T*>(policy->get_value(&object)); 
               return *r;
               }
 
            /// Returns true if the any contains no value. 
            bool empty() const {
               return policy == anyimpl::get_policy<anyimpl::empty_any>();
               }
 
            /// Frees any allocated memory, and sets the value to NULL.
            void reset() {
               policy->static_delete(&object);
               //The following used to be:
               //policy = anyimpl::get_policy<anyimpl::empty_any>();
               //But why should the policy default to the big_any_policy?  Therefore,:
               policy = anyimpl::get_policy<unsigned int>();
               }
 
            /// Returns true if the two types are the same. 
            bool isA(const any& x) const {
               return policy == x.policy;
               }
            
            template<typename T>
            bool isA() {
               T temp;
               return isA(temp);
               }
         }; //End of 'any' struct definition
   }
 
#endif // RMW_CDIGGINS_ANY_HPP


modified 15-Jul-13 17:35pm.

QuestionAn overlooked usage case in new code PinmemberDavid O'Neil26-Jan-13 19:01 
QuestionA small helper function for you [modified] PinmemberDavid O'Neil24-Jan-13 17:53 
Questionerror: 'TYPE' was not declared in this scope Pinmemberyurenjimi198520-Nov-12 15:44 
AnswerRe: error: 'TYPE' was not declared in this scope PinmemberMember 968039912-Dec-12 12:10 
GeneralIt looks good, but is that dynamic typing? PinmemberJamming114-May-11 12:20 
GeneralRe: It looks good, but is that dynamic typing? PinmemberMetaVoid29-Sep-11 9:46 
GeneralMemory Leaks and Crashes when storing "non-primitiv" data! PinmemberRaute11-Aug-10 3:57 
GeneralRe: Memory Leaks and Crashes when storing "non-primitiv" data! PinmemberChristopher Diggins9-May-11 15:31 
GeneralQuestion on checking empty PinmemberMember 421168429-Mar-10 9:26 
GeneralPreventing Reallocations PinmemberAnand Krishnamoorthi18-Apr-07 6:15 
QuestionSmall Types Assignment Problem? PinmemberAnand Krishnamoorthi18-Apr-07 6:10 
AnswerRe: Small Types Assignment Problem? Pinmemberxryl6695-Sep-07 2:28 
GeneralCompile problem with cast PinmemberNeville Franks17-Jul-06 13:15 
GeneralRe: Compile problem with cast Pinmemberpablo_mag17-Jul-06 14:00 
GeneralRe: Compile problem with cast [modified] PinmemberNeville Franks17-Jul-06 14:31 
GeneralRe: Compile problem with cast [modified] Pinmemberpablo_mag17-Jul-06 15:55 
GeneralRe: Compile problem with cast PinmemberNeville Franks17-Jul-06 18:53 
GeneralCompile error with const char* Pinmemberfarshizzo26-Apr-06 7:23 
GeneralRe: Compile error with const char* Pinmemberpablo_mag17-Jul-06 13:47 
GeneralUpdate Posted Pinmemberpablo_mag18-Sep-05 22:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 9 May 2011
Article Copyright 2005 by Christopher Diggins
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid