I have string tags in my code that are converted to numbers and used to search values in a tag-value structure.
I have something like this:
void foo(
Not sure that you can. Is the list of possible tags small? Even if not, is it small most of the time. If so, you can use template specialization on a subset of the tags
template<char *tag>
int toNumber() {
return toNumber(tag);
}
template<>
int toNumber<"0">() {
return 0;
}
template<>
int toNumber<"1">() {
return 1;
}
(caveats: my specialization syntax might be wrong, and I have no idea if this works for char*)
If the string literal is known at compile time, then there is probably no reason to use it as a string literal. You may use enumeration or named integral constants.
If the string is passed to the search function by a variable and it is not known at compile time, then there is no way to do the toNumber()
resulution at compile time. Then a good solution is to use some kind of a dictionary (e.g. std::map<std::string, int>
)
Althrough not compile time, I think this is fast enough for you;
void foo()
{
const static auto someTagN = toNumber("SomeTag");
type value = internal_search(someTagN );
}
You cannot operate on string literals at compile-time, so what you want isn't feasible in the way you suggested. However, if you're contemplating to process these strings at compile-time, then this means you know all strings at compile-time, and from that you might arrive at acceptable approximations to what you want.
The code you showed implies that the number generation (let's call it a hash) is invoked every time someone searches for a tag. Would reducing this to one invocation be acceptable? If so, you could define constants and use these instead of strings:
const int SomeTag = toNumber("SomeTag" );
const int SomeOtherTag = toNumber("SomeOtherTag" );
const int YetAnotherTag = toNumber("YetAnotherTag");
// ...
Then, simply replace all occurances of search("SomeTag")
by search(SomeTag)
.
If there's a great number of tags, typing the above could be very tedious, in which case a macro might help:
#define DEFINE_TAG(Tag_) const int Tag_ = toNumber(#Tag_);
DEFINE_TAG(SomeTag);
DEFINE_TAG(SomeOtherTag);
DEFINE_TAG(YetAnotherTag);
// ...
#undef DEFINE_TAG
It sounds like what you want is Boost.MPL's boost::mpl::string. It would be more-or-less trivial to write a metafunction to convert an mpl::string
to an integral type at compile time using mpl::fold (or fail to compile if the string literal does not represent a valid integral value).
EDIT:
I'm not entirely sure what you're looking for, so here is effectively two different answers depending on interpretation:
IF what you're looking for is compile-time string-to-integral-value conversion (e.g. so "425897"
could be recognized as the integral constant 425897
at compile time), then one can use Boost.MPL as I suggested:
#include <cstddef>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/char.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/minus.hpp>
#include <boost/mpl/negate.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/reverse_fold.hpp>
#include <boost/mpl/size_t.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/vector.hpp>
namespace details
{
namespace mpl = boost::mpl;
typedef mpl::vector10<
mpl::char_<'0'>, mpl::char_<'1'>, mpl::char_<'2'>, mpl::char_<'3'>,
mpl::char_<'4'>, mpl::char_<'5'>, mpl::char_<'6'>, mpl::char_<'7'>,
mpl::char_<'8'>, mpl::char_<'9'>
> valid_chars_t;
template<typename IntegralT, typename PowerT>
struct power_of_10;
template<typename IntegralT, std::size_t Power>
struct power_of_10<IntegralT, mpl::size_t<Power> > : mpl::times<
power_of_10<IntegralT, mpl::size_t<Power - 1u> >,
mpl::integral_c<IntegralT, 10>
> { };
template<typename IntegralT>
struct power_of_10<IntegralT, mpl::size_t<1u> >
: mpl::integral_c<IntegralT, 10>
{ };
template<typename IntegralT>
struct power_of_10<IntegralT, mpl::size_t<0u> >
: mpl::integral_c<IntegralT, 1>
{ };
template<typename IntegralT, typename StringT>
struct is_negative : mpl::and_<
boost::is_signed<IntegralT>,
boost::is_same<
typename mpl::front<StringT>::type,
mpl::char_<'-'>
>
> { };
template<typename IntegralT, typename StringT>
struct extract_actual_string : mpl::eval_if<
is_negative<IntegralT, StringT>,
mpl::pop_front<StringT>,
mpl::identity<StringT>
> { };
template<typename ExtractedStringT>
struct check_valid_characters : boost::is_same<
typename mpl::find_if<
ExtractedStringT,
mpl::not_<mpl::contains<valid_chars_t, mpl::_> >
>::type,
typename mpl::end<ExtractedStringT>::type
> { };
template<typename ExtractedStringT>
struct pair_digit_with_power : mpl::first<
typename mpl::reverse_fold<
ExtractedStringT,
mpl::pair<mpl::vector0<>, mpl::size_t<0> >,
mpl::pair<
mpl::push_back<
mpl::first<mpl::_1>,
mpl::pair<mpl::_2, mpl::second<mpl::_1> >
>,
mpl::next<mpl::second<mpl::_1> >
>
>::type
> { };
template<typename IntegralT, typename ExtractedStringT>
struct accumulate_digits : mpl::fold<
typename pair_digit_with_power<ExtractedStringT>::type,
mpl::integral_c<IntegralT, 0>,
mpl::plus<
mpl::_1,
mpl::times<
mpl::minus<mpl::first<mpl::_2>, mpl::char_<'0'> >,
power_of_10<IntegralT, mpl::second<mpl::_2> >
>
>
> { };
template<typename IntegralT, typename StringT>
class string_to_integral_impl
{
BOOST_MPL_ASSERT((boost::is_integral<IntegralT>));
typedef typename extract_actual_string<
IntegralT,
StringT
>::type ExtractedStringT;
BOOST_MPL_ASSERT((check_valid_characters<ExtractedStringT>));
typedef typename accumulate_digits<
IntegralT,
ExtractedStringT
>::type ValueT;
public:
typedef typename mpl::eval_if<
is_negative<IntegralT, StringT>,
mpl::negate<ValueT>,
mpl::identity<ValueT>
>::type type;
};
}
template<typename IntegralT, typename StringT>
struct string_to_integral2
: details::string_to_integral_impl<IntegralT, StringT>::type
{ };
template<typename IntegralT, int C0, int C1 = 0, int C2 = 0,
int C3 = 0, int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0>
struct string_to_integral : string_to_integral2<
IntegralT,
boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7>
> { };
Usage would look like:
type search(int tag) { /*impl... */ }
void foo()
{
type value = search(string_to_integral<int, '4258','97'>::value);
}
// OR, if you still want to maintain the separation
// between `search` and `internal_search`
type internal_search(int tag) { /*impl... */ }
template<typename TagStringT>
type search()
{
return internal_search(string_to_integral2<int, TagStringT>::value);
}
void foo()
{
typedef boost::mpl::string<'4258','97'> tag_t;
type value = search<tag_t>();
}
Support for negative numbers is implemented, support for overflow detection is not (but your compiler will probably give a warning).
IF what you're looking for is compile-time string-to-integral-value mapping (e.g. so "SomeTag"
could be recognized as the integral constant 425897
at compile time), then Boost.MPL still solves the problem, but all string-to-integral-value mappings must be known at compile time and registered centrally:
#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/void.hpp>
namespace details
{
namespace mpl = boost::mpl;
typedef mpl::map<
mpl::pair<
mpl::string<'Some','Tag'>,
mpl::integral_c<int, 425897>
>,
mpl::pair<
mpl::string<'Some','Othe','rTag'>,
mpl::integral_c<int, -87>
>,
mpl::pair<
mpl::string<'AnUn','sign','edTa','g'>,
mpl::integral_c<unsigned, 7u>
>
> mappings_t;
template<typename StringT>
struct map_string_impl
{
typedef typename mpl::at<
mappings_t,
StringT
>::type type;
BOOST_MPL_ASSERT_NOT((boost::is_same<type, mpl::void_>));
};
}
template<typename StringT>
struct map_string2 : details::map_string_impl<StringT>::type { };
template<int C0, int C1 = 0, int C2 = 0, int C3 = 0,
int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0>
struct map_string : map_string2<
boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7>
> { };
Usage would look like:
type search(int tag) { /*impl... */ }
void foo()
{
type value = search(map_string<'Some','Tag'>::value);
}
// OR, if you still want to maintain the separation
// between `search` and `internal_search`
type internal_search(int tag) { /*impl... */ }
template<typename TagStringT>
type search()
{
return internal_search(map_string2<TagStringT>::value);
}
void foo()
{
typedef boost::mpl::string<'Some','Tag'> tag_t;
type value = search<tag_t>();
}
mappings_t
is what needs to be edited to maintain your string-to-integral-value mappings, and, as demonstrated, the mapped integral values need not all be of the same underlying type.
In either case, because the mapping is done at compile time, search
/internal_search
(the one with the real implementation taking an int
) could be made to take the integral value as a template parameter rather than as a function parameter if doing so makes sense for its implementation.
Hopefully this answers your questions.
I know it is probably not fashionable, but you can generate a hash-table ahead of time. I like to use gperf to generate the plumbing there.
I know know. You wanted something to make compilation last longer... Just saying :)