FormalSignature
represents the arguments for a function in an interpreted language. You can imagine we're writing the interpreter in C++, and want to implement a few built-in functions in C++ rather than in the interpreted language. To make that work safely, FormalSignature
gives us a mapping from types in the interpreted language to types in C++.
Each parameter to the built-in is represented by a ParameterSpec
, templated on the C++ type and containing some fields to represent the parameter names and the type in the embedded language.
A FormalSignature
for Euclid's Greatest Common Divisor algorithm might look like this:
const FormalSignature<
ParameterSpec<unsigned int>,
ParameterSpec<unsigned int>
> Gcd{
"GCD", "Compute the greatest common divisor of the first two arguments. Place the result in the third",
ParameterSpec<unsigned int>{
"a", // name of the first arg
"positive integer", // typeId for the embedded language
InputOrOutput::Input
},
ParameterSpec<unsigned int>{
"b", "positive integer", InputOrOutput::Input
},
ParameterSpec<unsigned int>{
"d", "positive integer", InputOrOutput::Output
}
};
In order to invoke one of these built-ins, the FormalSignature
must be bound to actual values that exist within the runtime language. We call this operation connect
and pass in a vector<string>
of the names (of variables in the interpreted language) that the function is being called with. This produces an ActualSignature
. (We're using "formal" and "actual" parameters in the programming language sense. The formals are the names internal to the built-in. The actuals are the values on which the function is invoked.)
(Solved this using https://github.com/BlackMATov/invoke.hpp)
We have a nice solution (thanks to Scott) for converting ParameterSpec<T>
s into ActualParameter<T>
s. It produces a std::tuple<ActualParameter<T>, ActualParameter<U>, ...>
as we need. But it accepts a variable number of ParameterSpec<T>
arguments. In a FormalSignature
, I have them stored in a tuple.
How can I apply this tuple of arguments into the connect
function. A minimal example removed from context is in variadic.cpp.
I'm okay with explicitly listing out the template parameters when instantiating a FormalSignature
. However, I don't want to have to repeat that information when I create an ActualSignature
using FormalSignature<Ts...>::connect
, nor when I write a class that will own an ActualSignature
. The signatures in the actual code frequently have 10-15 parameters, and my goal with this refactor is to avoid duplicating the knowledge of their names and order.
How can I expose a template alias, FormalSignature<Ts...>::ActualSignatureT
that has all the template parameters filled in? For example:
using Example = FormalSignature<
ParameterSpec<unsigned int>,
ParameterSpec<string>,
ParameterSpec<double>
>;
static_assert(std::is_same<
Example::ActualSignatureT,
ActualSignature<
ActualParameter<unsigned int>,
ActualParameter<string>,
ActualParameter<double>
>
>::value);
I feel like I'm close with this, but can't quite make it work:
// AsParam converts from a ParameterSpec<T> to an ActualParameter<T> (this part works)
template<typename T>
struct AsActualParam {
using Param = T;
};
template <template <typename> class PSpec, typename T>
struct AsActualParam<PSpec<T>> {
static_assert(std::is_same<PSpec<T>, ParameterSpec<T>>::value, "AsActualParam can only be used with a ParameterSpec<T>");
using Param = ActualParameter<T>;
};
// succeeds
static_assert(std::is_same<
AsActualParam<ParameterSpec<double>>::Param,
ActualParameter<double>
>::value);
Then, we need to do the same thing for a parameter pack of ParamSpec...
template <
class PSpec, // does this need to be template<typename> class PSpec?
typename... Rest>
struct AsActualSignature {
using ActualSignatureT = ActualSignature<
// Pretty sure the beginning is right.
AsParam<PSpec>::Param,
// This line feels wrong, ActualSignatureT is a concrete type, not a parameter pack
AsActualSignature<Rest...>::ActualSignatureT...
>;
};
// also, feels like I need a base case for AsActualSignature?
Another potential solution
```cpp
template <typename... PSpecs>
struct AsActualSignature {
using ActualSignatureT = ActualSignature<
(AsParam<PSpecs>::Param)...
// I want the line above to expand PSpecs to produce
// AsParam<PSpecs0>::Param, AsParam<PSpecs1>::Param, AsParam<PSpecs2>::Param, ...
// but it doesn't looks like that's a supported type of expansion
>;
};
static_assert(std::is_same<
ActualSignature<ActualParameter<double>, ActualParameter<int>>,
AsActualSignature<ParameterSpec<double>, ParameterSpec<int>>::ActualSignatureT
>::value);
// This is the goal, but it currently fails. static_assert(std::is_same< AsActualSignature< ParameterSpec, ParameterSpec, ParameterSpec
::ActualSignatureT, ActualSignature< ActualParameter, ActualParameter, ActualParameter
::value);
I've been playing with this problem in [main.cpp](#file-main-cpp)