C++ compile time function execution
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()
{
type value = search("SomeTag");
}
Where search is defined like this:
开发者_StackOverflow社区type search(const char* tag)
{
return internal_search(toNumber(tag));
}
Because all the time tag is constant at compile time I want to remove the call that converts the tag to a number from search function. I know it is possible to execute some simple functions at compile time using templates (http://en.wikipedia.org/wiki/Compile_time_function_execution), but I don't know exactly how to iterate through a null terminated string and keep intermediate values in the template. Can you give a simple sample that iterates a null terminated string and adds the chars in a public variable please?
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.
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
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 );
}
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 :)
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*)
精彩评论