0

I have this C++ function object:

static unsigned long long const FNV_OFFSET_BASIS = 14695981039346656037ul;
static unsigned long long const FNV_PRIME        = 1099511628211ul;

struct fnv_hash {
    typedef unsigned long long value_type;

    fnv_hash( value_type *result ) {
        _result = result;
        *_result = FNV_OFFSET_BASIS;
    }

    void operator()( char c ) {  // Fowler–Noll–Vo FNV-1a hash function
        *_result ^= c;           //   see: https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
        *_result *= FNV_PRIME;
    }

private:
    value_type *_result;
};

that can be used like:

std::string const message = "hello, world";
hash::value_type hash_val;

std::for_each( message.begin(), message.end(), fnv_hash( &hash_val ) );

If I wanted to convert the function object to a lambda, there's the problem of initializing the captured variable hash_val to FNV_OFFSET_BASIS only when the underlying, compiler-generated lambda object is constructed. The only thing I can think of to do that is using yet another lambda:

std::for_each( message.begin(), message.end(),
    [&hv = [&hash_val]() -> decltype((hash_val)) {
               return hash_val = FNV_OFFSET_BASIS;  // Initialize and return reference to hash_val.
            }()                                     // Call the inner lambda.
           ]( char c ) {
        hv ^= c;
        hv *= FNV_PRIME;
    }
);

This works, but is there a cleaner way to to this?


Note: the example given is for pedagogical reasons only. The point of the question is how one can "initialize" a lambda in general --- and not how to solve this particular hash example.

Note: "It can't be done" is an acceptable answer. I want an answer in the event someone asks me a question similar to: what are the case(s) where one ought to still use a function object as opposed to a lambda?

8
  • 3
    Since at this point the calling code needs to be aware of FNV_OFFSET_BASIS anyway, won't it be easiest to simply initialize hash_val before passing it to the lambda? Commented Jul 20, 2020 at 22:02
  • This sure looks like a job for std::accumulate... Commented Jul 20, 2020 at 22:33
  • @IgorTandetnik Easiest? Maybe. But I'd prefer code that guarantees the user can't get it wrong. Commented Jul 20, 2020 at 23:16
  • 1
    @MooingDuck Feel free to swap out the example for some other example where the lambda requires some initial state. Whether I can use std::accumulate for this particular example really isn't the point of the question. Commented Jul 20, 2020 at 23:17
  • 1
    Do you believe that making the user write that two-level lambda is an improvement? If correctness is your goal, why not just keep the named functional, fnv_hash? Since it's nicely encapsulated, it'd require some effort to misuse. Commented Jul 20, 2020 at 23:24

3 Answers 3

1

You say the fact you can use std::accumulate is only for this specific example, but I'm skeptical. The only real difference between std::for_each and std::accumulate is that std::accumulate makes state explicit, including handling initialization. I think it is the solution in all cases.

static unsigned long long const FNV_OFFSET_BASIS = 14695981039346656037ul;
static unsigned long long const FNV_PRIME        = 1099511628211ul;

struct fnv_hash {
    typedef unsigned long long value_type;
    value_type operator()(value_type _result, char c) {  // Fowler–Noll–Vo FNV-1a hash function
        _result ^= c;           //   see: https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
        _result *= FNV_PRIME;
        return _result;
    }
};

int main() {
std::string const message = "hello, world";
fnv_hash::value_type hash_val 
    = std::accumulate( message.begin(), message.end(), FNV_OFFSET_BASIS, fnv_hash() );
}

http://coliru.stacked-crooked.com/a/0108d3fb96f1b7a0

accumulate takes a binary operator, but the first parameter is the entry in the container, and the second parameter is whatever state you need: in your case it's simply value_type _result. Though the concept extends to arbitrary state without issue.

Replacing this with a lambda is clearly trivial now:

value_type hash_val = std::accumulate( message.begin(), message.end(), FNV_OFFSET_BASIS, 
    [](value_type _result, char c) {  // Fowler–Noll–Vo FNV-1a hash function
        _result ^= c;           //   see: https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
        _result *= FNV_PRIME;
        return _result;
    });

http://coliru.stacked-crooked.com/a/6f9b78f8f21a2e0e

Sign up to request clarification or add additional context in comments.

4 Comments

@PaulJ.Lucas: I just noticed that. Done.
I ran the lambda version against the original code and it doesn't produce the same result (though it's not obvious to me what's different).
@PaulJ.Lucas The input parameters of both the functor and lambda are backwards, that is why the results are wrong. The parameters need to be (value_type _result, char c) not (char c, value_type _result)
@RemyLebeau: Whoopsie! :(
0

At the cost of some efficiency, you could use a second, pass-by-value parameter to initialise hash_val:

std::for_each( message.begin(), message.end(),
    [&hash_val, initialised = false](char c) mutable {
        if (!initialised)
        {
            hash_val = FNV_OFFSET_BASIS;
            initialised = true;
        }
        hash_val ^= c;
        hash_val *= FNV_PRIME;
    }
);

But I prefer the functor.

Comments

0

Simply initialize the hash_val variable at the point where it is declared, before the lambda captures it. There is no need for the lambda to initialize it, only to mutate it, eg:

using hash_value_type = unsigned long long;
static hash_value_type const FNV_OFFSET_BASIS = 14695981039346656037ul;
static hash_value_type const FNV_PRIME        = 1099511628211ul;

std::string const message = "hello, world";
hash_value_type hash_val = FNV_OFFSET_BASIS;

std::for_each(
    message.begin(), message.end(),
    [&hash_val](char c){
        hash_val ^= c;
        hash_val *= FNV_PRIME;
    }
);

Alternatively, use std::accumulate() instead of std::for_each(), then you don't even need to capture the hash_val variable at all, eg:

using hash_value_type = unsigned long long;
static hash_value_type const FNV_OFFSET_BASIS = 14695981039346656037ul;
static hash_value_type const FNV_PRIME        = 1099511628211ul;

std::string const message = "hello, world";
hash_value_type hash_val = std::accumulate(
    message.begin(), message.end(),
    FNV_OFFSET_BASIS,
    [](hash_value_type val, char c) {
        val ^= c;
        val *= FNV_PRIME;
        return val;
    }
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.