9

I am trying to use boost::any to encapsulate the sqlite return values. I then tried to write a loop to print these.

My first thought was to do something like:

for(boost::any field: row) {
  switch(field.type()) {
      case typeid(double):
          double value = any_cast<double>(field);
          ...
          break;
      case typeid(other type):
          ...
  }
}

Now for the experienced programmer it becomes obvious that this can not work since typeid returns an instance rather than a numeric id. After some research I figured I might try either typeid(...).hash_code() however this is not sufficiently constexpr qualified (Beside the danger of hash collision).

Questions

  1. Is there a better way than building an excessive if ... else ... labyrinth to handle objects based on their typeid?
  2. Is there a reason why hash_code is not a const_expr? Is this a result of the separate compilation of object files?
  3. What is the use of std::type_index? Considering that it only provides some additional operators (<, <=, >, >=) why was it not possible to integrate its functionality with std::type_info?
ted
  • 4,791
  • 5
  • 38
  • 84
  • 2
    Won't `typeid(field)` just return the type ID for `boost::any`? – cdhowie Dec 08 '14 at 23:08
  • `hash_code()` is not `constexpr` because `type_info` may be polymorphic. There was discussion about proposing a way to get a compile-time identifier for a type, but I don't think it went anywhere? – Collin Dauphinee Dec 08 '14 at 23:58
  • @ted How do you feel about solutions that are effectively a big nested if/else block, but that hide this behind some nice C++11 syntax? (Assuming you have access to C++11.) – cdhowie Dec 09 '14 at 00:04
  • 1
    Use a std::map to map type_index to functors. – kec Dec 09 '14 at 02:43
  • If this is specific to SQLite, why don't you use the return value of [sqlite3_column_type](http://www.sqlite.org/c3ref/column_blob.html)? – CL. Dec 09 '14 at 08:29
  • @CollinDauphinee: I would be really interested in that. I poked arround a bit and thought that it should be possible to generate a unique typeid across compile units in a way some compilers manage templates (https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html) (note I am not referring to the gcc approach of multiple instances of the template, but the approaches before that map all instantiations across compile units to one, by using this technique and using a pointer to the typeid one should get a unique id) – ted Dec 09 '14 at 10:53
  • @CollinDauphinee: Actually that migh be an Idea, adding a class `enableTypeIDPointer { public: static uintptr_t id() { return (uintptr_t)(enableTypeIDPointer)this; } }` and inheriting from it might give me what I am looking for (Assuming I get the compiler to instantiate templates only once). – ted Dec 09 '14 at 11:00
  • @cdhowie I would love it. (after all a case is also a big nested if-else behind nicer syntax if you have a break in every non-empty case) – ted Dec 09 '14 at 11:02
  • @kec: I might try that. In general a nice Idea to do it. However for the SQLite case with only 5(+1) cases (Blob, Text, Int64, Double, null, CATCHALL for Invalid) it seems a bit heavy. I feel that by just ordering the cases right after expected frequency I would get more performance than I could get with the indirection of actually calling different functions instead of just inlining the code. – ted Dec 09 '14 at 11:05
  • 1
    [Boost Variant](http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html) and [Boost TypeIndex](http://www.boost.org/doc/libs/1_57_0/doc/html/boost_typeindex.html). I'll just leave that here – sehe Dec 09 '14 at 12:05
  • @Collin Do you mean [this](http://stackoverflow.com/a/4194298/256138)? – rubenvb Dec 09 '14 at 13:20
  • @ted I posted my prototype as an answer. Note that switch is usually not the same as if/else in terms of performance; it's a computed goto instead of a sequence of checks, so the performance of switch is approximately O(1) time complexity compared to O(n) for if/else (variable on the number of cases). However, as in cases like this, computed goto is not always possible. – cdhowie Dec 09 '14 at 15:30
  • @cdhowie: thats an interesting special case optimization (I assume it works by having an array and using the switch variable as an index), however as you said it does not seem always possible. Am I correct with my guess or are you referring to a different technique with `computed goto`. If so I imagine it as quite costly to implement this for the case where you have small and huge case ids with large gaps inbetween (large empty table). – ted Dec 09 '14 at 16:04
  • 1
    The technique I'm referring to by "computed goto" is one of the possible ways that `switch` can be compiled. It uses a jump table, the control variable being what is used to find the entry in the table. The compiler may choose to compile a switch statement using a series of conditionals (exactly like if/else) if the generated jump table wouldn't be sufficiently dense. ([Further reading](http://stackoverflow.com/q/14067547/501250)) – cdhowie Dec 09 '14 at 16:17

2 Answers2

8

I have a feeling you are looking for boost variant and static visitations.

Since variants weren't mentioned, this might be worth posting as an answer. Demonstruction:

Live On Coliru

#include <sstream>
#include <iostream>

#include <boost/variant.hpp>

using namespace boost;

struct Nil {};
using blob_t = std::vector<uint8_t>;
using field_value_t = boost::variant<Nil, double, char const*, long, blob_t/*, boost::date_time, std::vector<uint8_t>*/>;

struct handler : static_visitor<std::string> {

    std::string operator()(double)      const { return "double"; }
    std::string operator()(char const*) const { return "C string (ew!)"; }
    std::string operator()(long)        const { return "long"; }
    std::string operator()(blob_t)      const { return "long"; }
    std::string operator()(Nil)         const { return "<NIL>"; }

    template<typename T>
    std::string operator()(T const&)    const { throw "Not implemented"; } // TODO proper exception
};

void handle_field(field_value_t const& value) {
    std::cout << "It's a " << apply_visitor(handler(), value) << "\n";
}

int main() {

    handle_field({});
    handle_field(blob_t { 1,2,3 });
    handle_field("Hello world");
    handle_field(3.14);

}

Prints

It's a <NIL>
It's a long
It's a C string (ew!)
It's a double
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Where is the advantage of `boost::varaint` over `boost::any`? What would be the best way to mark `null` values? (adding `void*` to the template parameters?) – ted Dec 09 '14 at 12:39
  • @ted Why `void*`? It's not C anymore :) `struct Nil {}; boost::variant` and you're on your way. Updated the sample **[Live On Coliru](http://coliru.stacked-crooked.com/a/de85c1ccac3ad6bc)** – sehe Dec 09 '14 at 14:05
  • 1
    Oh, re _`Where is the advantage`_: the advantage is that you leave the type discrimination to Boost Variant and never leave type safety, nor deal with potential hairiness surrounding typeid. – sehe Dec 09 '14 at 14:07
5

Here is an implementation of something similar to static visitation on boost::any, using C++11 lambdas:

#include <iostream>
#include <type_traits>
#include <boost/any.hpp>

template <size_t, typename...>
struct select_type { };

template <size_t index, typename First, typename... Types>
struct select_type<index, First, Types...> : public select_type<index - 1, Types...> { };

template <typename First, typename... Types>
struct select_type<0, First, Types...>
{
    using type = First;
};

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())> { };

template <typename Return, typename Class, typename... Args>
struct function_traits<Return (Class::*)(Args...) const>
{
    using result_type = Return;

    template <size_t argN>
    using argument_type = select_type<argN, Args...>;
};

template <typename... Functors>
struct any_call_impl
{
    static bool call(boost::any &, Functors const & ...)
    {
        return false;
    }

    static bool call(boost::any const &, Functors const & ...)
    {
        return false;
    }
};

template <typename FirstFunctor, typename... Functors>
struct any_call_impl<FirstFunctor, Functors...>
{
    static bool call(boost::any & v, FirstFunctor const & first, Functors const & ... rest)
    {
        using arg = typename function_traits<FirstFunctor>::template argument_type<0>::type;
        using arg_bare = typename std::remove_cv<typename std::remove_reference<arg>::type>::type;

        if (v.type() == typeid(arg_bare)) {
            first(*boost::any_cast<arg_bare>(&v));
            return true;
        }

        return any_call_impl<Functors...>::call(v, rest...);
    }

    static bool call(boost::any const & v, FirstFunctor const & first, Functors const & ... rest)
    {
        using arg = typename function_traits<FirstFunctor>::template argument_type<0>::type;
        using arg_bare = typename std::remove_cv<typename std::remove_reference<arg>::type>::type;

        if (v.type() == typeid(arg_bare)) {
            first(*boost::any_cast<arg_bare>(&v));
            return true;
        }

        return any_call_impl<Functors...>::call(v, rest...);
    }
};

template <typename... Functors>
bool any_call(boost::any & v, Functors const & ... f)
{
    return any_call_impl<Functors...>::call(v, f...);
}

template <typename... Functors>
bool any_call(boost::any const & v, Functors const & ... f)
{
    return any_call_impl<Functors...>::call(v, f...);
}

int main(void) {
    boost::any a = 1;

    any_call(a,
        [](double d) { std::cout << "double " << d << std::endl; },
        [](int i) { std::cout << "int " << i << std::endl; }
    );

    return 0;
}

(Demo)

The idea is that you pass a boost::any or boost::any const as the first argument to any_call, and after that you pass multiple lambdas. The first lambda whose parameter type matches the type of object contained in boost::any will be called, and then any_call will return true. If no lambda matches, any_call will return false.

cdhowie
  • 158,093
  • 24
  • 286
  • 300