Trying & failing to make a function that returns functions... (C++11)

Discussion in 'C++' started by Mark, Mar 9, 2012.

  1. Mark

    Mark Guest

    Hi,

    I am trying to make a function that will create and return another
    function (a closure that captures some state).

    Here's the usage I want:

    std::vector<std::string> on_off{"on", "off"};
    StringValidatorFunction on_off_validator =
    make_string_set_validator(on_off.begin(), on_off.end());
    auto s = on_off_validator("on"); // s == "on"
    auto t = on_off_validator("awf"); // throws
    std::vector<std::string> color{"red", "green", "blue"};
    StringValidatorFunction color_validator =
    make_string_set_validator(color.begin(), color.end());
    auto r = color_validator("red"); // s == "red"
    auto y = color_validator("yellow"); // throws

    Here's what I've got so far:

    using StringValidatorFunction = std::function<std::string(const
    std::string&)>;

    template <class Iter>
    StringValidatorFunction make_string_set_validator(const Iter first,
    const Iter end)
    {
    std::unordered_set<std::string> valid_strings;
    std::for_each(first.begin(), first.end(),
    [&valid_strings](const std::string &s)
    {valid_strings.insert(s);});
    return [valid_strings](const std::string &s)->std::string{
    if (valid_strings.find(s) == valid_strings.end())
    throw ValueError("Invalid string '" + s + "'");
    return s;
    };
    }

    This is the first time I've tried to use lambdas (I'm just starting
    out with C++11), and GCC 4.7.0 isn't helpful (at least not to me):

    main.o: In function `main':
    main.cpp:(.text+0x253): undefined reference to
    `std::function<std::string ()(std::string const&)>
    make_string_set_validator<__gnu_cxx::__normal_iterator<std::string*,
    std::allocator<std::string> > >,
    __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string,
    std::allocator<std::string> > >)'
    collect2: error: ld returned 1 exit status

    Any suggestions welcome:)

    Thanks!
     
    Mark, Mar 9, 2012
    #1
    1. Advertisements

  2. Mark

    Mark Guest

    [snip]

    I'd made the silly mistake of putting the templated function in the
    source rather than the header:-(

    And realized that there's a slightly nicer usage possible:

    std::vector<std::string> on_off{"on", "off"};
    auto on_off_validator = make_string_set_validator(on_off);

    Using:

    template <class T>
    StringValidatorFunction make_string_set_validator(const T
    &valid_strings)
    {
    std::unordered_set<std::string> strings;
    std::for_each(std::begin(valid_strings), std::end(valid_strings),
    [&strings](const std::string &s){strings.insert(s);});
    return [strings](const std::string &s)->std::string{
    if (strings.find(s) == strings.end())
    throw ValueError("Invalid string '" + s + "'");
    return s;
    };
    }
     
    Mark, Mar 9, 2012
    #2
    1. Advertisements

  3. In principle there's nothing wrong with that, as long as you somehow
    end up instantiating the template implementation will all the types with
    which it's used in the program. Of course usually the easiest way to do
    that is to put the implementation in the header where it's declared.

    (It is, in fact, possible to simply declare (rather than define) a
    template in a header, use only this declaration in the different
    compilation units, and put the implementation of the template in one
    single compilation unit where it's explicitly instantiated for each
    used type. (C++ support a special syntax for explicit template
    instantiation.) This is akin to the (now defunct) export template
    mechanism, except that you have to do the instantiation manually, which
    makes it infeasible in most practical cases.)
     
    Juha Nieminen, Mar 9, 2012
    #3
  4. Mark

    BGB Guest


    <snip, example>

    although I am not really experienced with C++11 nor do I have an easy
    time following the code, it does look a little worrying:
    as I understand it, returning (C++11) closures in this way may not be safe?

    the issue is mostly where the space for any captured references is
    stored, and if it is copied along with the closure, or is somewhere
    "stable" (such as in the heap) vs somewhere unstable (say, on the stack).

    non-capturing lambdas are known safe (and may also be cast to function
    pointers);
    lamdas which capturing references to locals are known unsafe (to return);
    what about capturing values or references to data outside the parent frame?

    (both safe and unsafe implementations can be easily imagined, but does
    the standard mandate this case is safe? I don't actually know...).



    ironically (OT / side-note):
    my script VM doesn't have these issues (with closures), at the cost that
    everything is heap allocated (they can also be coerced into C-style
    function pointers as well). the tradeoff though is that they either have
    to be deleted/freed, or risk triggering the GC to do its thing (say, if
    closures are created in large numbers and/or at a rapid pace).

    technically, they are "executable objects", allocated in RWX
    (Read/Write/Execute) memory, with executable code (a "trampoline")
    preceding any data members (so, one is essentially jumping into/through
    a struct). sadly, they also have to be "locked"/"pinned" before they can
    be safely used as callbacks (if stored in non-GC'ed memory), which
    prevents them from being automatically GC'ed (allowing for a potentially
    VM breaking edge case).


    or such...
     
    BGB, Mar 9, 2012
    #4
  5. A lambda that captures by value is safe to outlive the scope of those
    values. The captured objects are copied to a local stack of the lambda,
    which lives for as long as the lambda itself. (How this is actually
    implemented internally probably depends on the compiler.)
     
    Juha Nieminen, Mar 9, 2012
    #5
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.