c++ - Order of function definition during template instantiation -
i have trouble understanding order of template instantiation. seems compiler not consider function if defined "too late." following steps illustrates main ideas of code below:
the framework should provide free function
convert<from, to>
if can find working overload functiongenerate
.the function
to<t>
shortcutconvert<from,to>
, should work ifconvert<from,to>
valid.users should able provide overload of
generate
, able useto
,convert
.
the corresponding code:
#include <string> #include <utility> #include <iostream> // if move code down below @ [*] location, works // expected. // ------------- framework code ------------- // can generated can converted string. template <typename from> auto convert(from const& from, std::string& to) -> decltype( generate(std::declval<std::back_insert_iterator<std::string>&>(), from) ) { to.clear(); auto = std::back_inserter(to); return generate(i, from); } // similar convert, except directly returns requested type. template <typename to, typename from> auto to(from const& f) -> decltype(convert(f, std::declval<to&>()), to()) { t; if (! convert(f, t)) throw std::invalid_argument("invalid conversion"); return t; } // ------------- user code ------------- // [*] support arithmetic types. template <typename iterator, typename t> auto generate(iterator& out, t i) -> typename std::enable_if<std::is_arithmetic<t>::value, bool>::type { // note: merely use std::to_string illustration purposes here. auto str = std::to_string(i); out = std::copy(str.begin(), str.end(), out); return true; } int main() { uint16_t s = 16; std::cout << to<std::string>(s) << std::endl; return 0; }
the problem in following code works if function generate
appears before definition of convert
, to
. how can work around problem?
maybe mental model wrong here, thought template when compiler sees to<std::string>(uint16_t)
, starts going backwards , instantiate needed. guidance appreciated.
the compiler not know of existence of generate
time sees definition of convert
, to
, have guessed yourself. contrary thought, doest not though sort of put definitions of convert
, to
"on hold" until sees generate
is. workaround problem need forward declare generate
, can done using following construction:
template <typename iterator, typename t> auto generate(iterator& out, t i) -> typename std::enable_if<std::is_arithmetic<t>::value, bool>::type;
this should appear right before definition of convert
, compiler knows generate
exists , function time compiles convert
, to
. way compiler can check syntax , guarantee valid call generate
, before knows generate does, since needs @ point check if types of arguments of return value match, according rules defined language standard.
by doing this, naturally enforce specific type signature generate
(remember compiler required check types when compiles convert
, to
!). if don't want that, , don't, best approach expect further template argument convert
, to
likewise, expect callable, is, can use in function call:
template <typename from, typename generator> auto convert(from const& from, std::string& to, generator generate) -> decltype( generate(std::declval<std::back_insert_iterator<std::string>&>(), from) ) { to.clear(); auto = std::back_inserter(to); return generate(i, from); }
these kind of objects commonly known callable objects.
the drawback approach because c++ unfortunately not support concepts yet, can't enforce requirements callable object generate
should attend to. nonetheless, approach std library uses algorithms.
the advantage of approach can flexible, any possible callable object, minimally attends type requirements, can used. includes free functions, function objects, member functions through binding, among others. not mention user absolutely free choose name wants callable object instead of being forced use generate
, initial idea require if valid c++.
now call modified version of convert
using free function generate
you defined, that:
to<std::string>(s, generate<std::back_insert_iterator<std::string>, uint16_t>);
that isn't nice, because since must explicitly state template arguments, approach fails take full advantage of fact generate
template function. fortunately inconvenience can overcome use of function objects, following instance:
struct generator { template <typename iterator, typename t> auto operator()(iterator& out, t i) -> typename std::enable_if<std::is_arithmetic<t>::value, bool>::type { // note: merely use std::to_string illustration purposes here. auto str = std::to_string(i); out = std::copy(str.begin(), str.end(), out); return true; } };
the previous call become simply
to<std::string>(s, generator());
taking full advantage of tamplate nature.
at rate, if got idea correctly, part of code of responsability of user, she, deserves, have full autonomy decide way prefers.
Comments
Post a Comment