#Error passing a templated function as a template parameter

196 messages · Page 1 of 1 (latest)

fluid onyx
#

Hi, I am writing a simple benchmark wrapper that can be run with multiple possible functions (that all fit the same signature). I am trying to use template parameters instead of a function pointer or std::function here, because in general this method will allow the compiler to inline the function given rather than calling it through the pointer. My wrapper looks like this.

template <typename Dispatch>
void runBenchmark(const Context<Dispatch>& ctx, const auto&& benchmark, const std::string_view name, bool bda) {
    static constexpr size_t BENCHMARK_ITERATIONS = 1000;
    const auto buffers = createBuffers(ctx, bda);
    std::array<double, BENCHMARK_ITERATIONS> times{};

    for (size_t i = 0; i < BENCHMARK_ITERATIONS; i++) {
        times[i] = static_cast<double>(benchmark(ctx, buffers).count()) / 1e6;
    }

    const double mean = std::reduce(times.begin(), times.end(), 0.0) / BENCHMARK_ITERATIONS;
    const double stddev = std::accumulate(times.begin(), times.end(), 0.0, [mean](double sum, double x) { return sum + (x - mean) * (x - mean); }) / BENCHMARK_ITERATIONS;
    std::println("{} mean: {}ms", name, mean);
    std::println("{} stddev: {}ms", name, stddev);
}
#

When I call my higher-order function, I get the following error:

/home/xxx/bda_benchmark/main.cpp:276:5: error: no matching function for call to 'runBenchmark'
  276 |     runBenchmark(ctx, descriptorBenchmark, "Descriptor", false);
      |     ^~~~~~~~~~~~
/home/xxx/bda_benchmark/main.cpp:197:6: note: candidate template ignored: couldn't infer template argument 'benchmark:auto'
  197 | void runBenchmark(const Context<Dispatch>& ctx, const auto&& benchmark, const std::string_view name, bool bda) {

One possible snafu is that the function I am passing in is itself generic over Dispatch (the Dispatch types should be the same). The signature is:

template <typename Dispatch>
std::chrono::nanoseconds descriptorBenchmark(const Context<Dispatch>& ctx, const Buffers& buffers) {
    ...
}

Is there any way I can get this to work? I was thinking maybe by restricting the function in the runBenchmark signature somehow using concepts, but I'm not sure how to do that.

#

ah never mind I figured it out

#

if I explicitly specify the template type when passing it in by calling runBenchmark(ctx, descriptorBenchmark<MyDispatchType>, "Descriptor", false); the compiler will be happy

#

just a confusing error message as usual for templates lol

#

still would be nice if I could specify in some way the function signature and that those two types should be the same, maybe that's a concept thing

keen fiberBOT
#

@fluid onyx has reached level 1. GG!

fluid onyx
#

I will look into it but if anyone knows more about it please post here :)

#

might be more trouble than it's worth here actually :p

hoary flare
#

what

#

based on what i read, i think you called it wrong.

keen fiberBOT
#

@hoary flare has reached level 3. GG!

hoary flare
#

try calling with the template?

#

idk

blissful flicker
fluid onyx
#

which makes sense

#

so you just have to specify the template parameter and it's fine

#

I mean it should be able to try them all theoretically but templates are complicated

blissful flicker
fluid onyx
#

yeah that was my mistake

blissful flicker
#

"the function signature and that those two types should be the same"

fluid onyx
#

yes but it's only enforced by the duck typing working out

#

in rust for example I could specify explicitly that Benchmark should be a function with that specific signature also dependent on the other generic parameter for instance

#

but I think I'm just more used to rust and have to adjust to the duck typing way of doing things

blissful flicker
#

yeah you can definitely create a concept for tihs

#

sec

fluid onyx
#

I'm just very unfamiliar with concepts :p

#

rust would be something like

fn run_benchmark<D, F>(ctx: &Context<D>, benchmark: F, name: &str, bda: bool)
    where F: Fn(&Context<D>, &Buffers) -> Duration
keen fiberBOT
#

@fluid onyx has reached level 2. GG!

blissful flicker
#

so we can just check if our callable can accept 2 args, namely Context and Buffers, and returns a std::chrono::nanoseconds

template<typename Dispatch, typename Bench>
concept Benchmark = requires(Bench f, const Context<Dispatch>& ctx, const Buffers& buffers) {
  {std::forward(f)(ctx, buffers)} -> std::same_as<std::chrono::nanoseconds>;
};
#

horrible naming of the args/signatures

#

but it works i think

#

if you arent familiar with the signature of how a concept is defined, you put all of the necessary args in the requires clause, and inside you check if an expression satisfies a constraint: { expr } -> constraint

#

what we're doing is that we're checking if calling f with ctx and buffers returns a std::chrono::nanoseconds

#

thats pretty much our function signature

#

i havent checked if this works ill leave that up to youi lmaoo

fluid onyx
#

giving me inscrutiable compiler errors...

#

I'll see if I can work with that to figure it out lol ty

blissful flicker
#

yeah quite expected i have no clue on what your codebase looks like

#

but yheah this is what you do

fluid onyx
#

I have no clue what std::forward does tbh

#

I've heard it's like std::move or something

#

but not

blissful flicker
#

std::forward "forwards" the arguments to a function in such a way that they preserve their cvref qualifiers

#

imagine you have a function as such

#
template<typename T>
void foo(T x);
#

this will pass anything by value

#

doesnt matter if youre calling it with a const, a const ref, a refref

#

to fix this, a mechanism was introduced called the universal type

#
template<typename T>
void foo(T&& x);
fluid onyx
#

core parts of my brain are already screaming to throw it all away and write everything in C99 but go on :p

blissful flicker
#

this T&& is not an rvalue reference

fluid onyx
blissful flicker
#

i know

#

its a bit odd

fluid onyx
#

so what does && behave completely differently depending on whether it's on a template parameter or not

blissful flicker
#

yes

fluid onyx
#

what the fuck

#

who decided that was a good idea

blissful flicker
#

they contemplated on using T&&&

#

but it was just a mouthful

fluid onyx
#

fucking c++ standards committee

blissful flicker
#

you get used to it

#

trust me

#

you see a typenamerefref and it clicks immediately

#

tho yeah it is weird

#

so what this T&& says is that whatever it receives, it will treat it with its proper qualifiers

#

and std::forward is the function that you use to pass to it

fluid onyx
#

so the caller can choose whether they want to pass by reference or value

blissful flicker
#

yes

fluid onyx
#

and forward lets you do in a wrapper function basically

blissful flicker
#

yeah

#

theres a lot of nuance to this

fluid onyx
#

or pass the universal type along to lower functions in your chain

blissful flicker
#

so youll see std::forward being used inside functions mostly

fluid onyx
#

I mean it makes sense but I'm pretty sure there's no language with as complicated and intricate syntax around this as C++ just for the sake of more implicit behavior lol

blissful flicker
#
template<typename T>
void foo(T&& x) {
  bar(std::forward<T>(x));
}
#

so what this says is that

#

we will pass x to bar depending on what kind of qualifiers it has

#

if you pass to it normally, it'll get implicitly converted or have qualifiers stripped

#

if youre using templates a lot, you need to pay attention to this

blissful flicker
#

itll just be passed as a value

blissful flicker
#

a lot of it is backwards compatibility + the committee scrambling to make tmp somewhat usable

#

this is not that much of an issue in modern c++, as you can constexpr pretty much

#

anything

#

not everything

#

but that day will come

#

just remember this signature for concepts

#
template<typename T>
concept Concept = requires(whichever arguments you need) {
  { expr } -> (satisfies) constraint;
};
fluid onyx
#

what does std::forward do in the code you gave then? there's no universal reference

#

if I remove it it does compile (had to switch the template args around a little tho)

blissful flicker
#

in your specific case it doesnt matter

#

but its a good thing to do if you plan on creating different types of benchmark callables

fluid onyx
#

well, it gives me a non deducable template error so :p

#

I'll just leave it out

blissful flicker
#

but if you dont, you can just do f(ctx, buffers)

#

yeah i dont really know what Buffers or Context looks like

fluid onyx
#

a little weirder than the way rust does things but

blissful flicker
#

youre just passing a template if youre doing th at

#

so you cant really

#

"fix it"

#

if you want to do it that wayu

fluid onyx
#

a sufficiently advanced compiler would be able to infer it

#

rust probably can

#

because the generic limits are more integrated into the language

blissful flicker
#
template <typename Dispatch, Benchmark bench>
void runBenchmark(const Context<Dispatch>& ctx, bench&& benchmark, const std::string_view name, bool bda) {
    static constexpr size_t BENCHMARK_ITERATIONS = 1000;
    const auto buffers = createBuffers(ctx, bda);
    std::array<double, BENCHMARK_ITERATIONS> times{};

    for (size_t i = 0; i < BENCHMARK_ITERATIONS; i++) {
        times[i] = static_cast<double>(benchmark(ctx, buffers).count()) / 1e6;
    }

    const double mean = std::reduce(times.begin(), times.end(), 0.0) / BENCHMARK_ITERATIONS;
    const double stddev = std::accumulate(times.begin(), times.end(), 0.0, [mean](double sum, double x) { return sum + (x - mean) * (x - mean); }) / BENCHMARK_ITERATIONS;
    std::println("{} mean: {}ms", name, mean);
    std::println("{} stddev: {}ms", name, stddev);
}

id do something like this with the concept

fluid onyx
#

instead of being a feature added on top of the whole turing complete template metaprogramming thing :p

blissful flicker
#

if you just copy and paste this in your codebase this will error though

#

most likely

blissful flicker
#

if you want to, pick up a templates book

#

josuttis has a good one

fluid onyx
#

that works, just have to make it template<typename Dispatch, Benchmark<Dispatch> F>

blissful flicker
#

beginner friendly too

#

yeah, because just "Benchmark" is a template

#

Benchmark<Dispatch> is an actual object

fluid onyx
#

yeah makes sense you have to specify concrete types

blissful flicker
#

a lot of the template errors pretty much boil down to

#

"id have no idea what type this is at compiletime"

#

just wait until you get into dependent names, thats very unintuitive as well

fluid onyx
#

sounds intimidating

blissful flicker
#

id read through modern c++ design by alexandrescu

#

and templates: the complete guide (josuttis)

fluid onyx
#

do people use concepts much in real world codebases these days?

blissful flicker
#

not a lot but theyre used yeah

fluid onyx
#

I know modules aren't really because of the lack of compiler support and since pch is good enough but concepts have been out for a little while

keen fiberBOT
#

@fluid onyx has reached level 3. GG!

blissful flicker
#

at least not in the codebasees ive seen

#

mostly boils down to

#

people not being that comfortable with the feature

fluid onyx
#

makes sense

blissful flicker
#

imo concepts are like insanely good

#

makes error messages that much easier to parse

fluid onyx
#

yeah

#

template errors can be completely inscrutiable without them

blissful flicker
#

btw id recommend using clang as well, sometimes

#

i think their template error messages are a bit more readable

fluid onyx
#

added bonus of -fsanitize=address

#

:p

#

I'm a clang fan

blissful flicker
#

i use gcc and clang interchangeably

#

honestly for debugging templates

#

spam static_asserts

#

use type_traits library

#

get familiar with concepts

fluid onyx
#

either that

#

or use zig or rust instead

#

:p

#

zig might have the same problems tho eventually since its fundamentally more similar to cpp templates than rust system

blissful flicker
#

i dont use rust but yeah their generics are pretty good from what ive heard

#

i mean, in newer codebases, tmp will be phased out eventually

#

in favor of auto and constexpr being more integrated in the language

fluid onyx
blissful flicker
#

do you work in rust?

fluid onyx
#

nah I just do a lot of rust projects for fun

#

I'm an unemployed recent grad actually

#

:p

blissful flicker
#

goat shit

fluid onyx
#

threads don't have this problem as much because you can scope them

blissful flicker
#

well that sucks

#

so are you just pigenholed into only doing compiletime programming

fluid onyx
blissful flicker
#

whats the alternative, really

fluid onyx
fluid onyx
blissful flicker
#

honestly just keep using concepts

#

id much rather see something like Callable&& f than auto&& f

fluid onyx
#

yeah

blissful flicker
#

you do have an alternative, abusing the shit out of std::enable_if and sfinae in general

#

which does also work but, again, people arent that comfortable with it

fluid onyx
#

that sounds like what concepts were meant to replace lol

blissful flicker
#

yeah

#

they have pretty weird signatures too

template<typename T,
         typename = std::enable_if_t<std::is_integral_v<T>>
fluid onyx
#

that is weird

blissful flicker
#

like, a lot of tmp isnt that hard

#

its just that most people dont need to learn it

#

therefore theyre kind of lost when they see it in their codebases

fluid onyx
#

it can be pretty confusing

#

honestly I think it can make library code kind of unreadable though

#

I like being able to C-b into my library functions and look at them and be able to see what the code does pretty fast

#

libraries that heavily use templates for performance/providing some nice to use interface can have code that's pretty hard to read

blissful flicker
#

yeah

#

but honestly thats just the standard library in general

#

boost source code, for example

#

is much easier to read

fluid onyx
#

I'm not sure if I'd agree with you there :p but maybe relatively so