As an exercise, I am designing a Goal-Oriented Action Planner in Unreal Engine 4. I am trying to setup my own structs which I can use to dynamically describe the world state using a variable-length list of data composed of individual variable types. I thought that variadic templated structs might be an interesting way to do this, but when implementing the value accessor I ran into problems.
I want it to work so that I can instantiate these structs at runtime, using FDataKeys<T>in the generic parameters for FFact<T...>. I then want to have a function GetValue<S>(string Key) which will recursively check the Data keys until a match is found. I will implement a flag parameter to indicate the success or failure of a search, but I’m getting a compile error in my scratch code that doesn’t make sense to me.
I know that I could setup a helper class with a static function to do this, but I’m curious as to why this isn’t working and how I would be able to implement it this way if possible.
Error Message:
38:42: warning: ISO C++ forbids use of 'auto' in parameter declaration [-Wpedantic] In instantiation of 'S FFact<T, R ...>::GetValue(const string&) [with S = char; T = std::basic_string; R = {}; std::string = std::basic_string]':
50:45: recursively required from 'S FFact<T, R ...>::GetValue(const string&) [with S = char; T = bool; R = {std::basic_string<char, std::char_traits, std::allocator >}; std::string = std::basic_string]'
50:45: required from 'S FFact<T, R ...>::GetValue(const string&) [with S = char; T = char; R = {bool, std::basic_string<char, std::char_traits, std::allocator >}; std::string = std::basic_string]'
67:37: required from here
46:25: error: cannot convert 'std::basic_string' to 'char' in return In member function 'S FFact<T, R ...>::GetValue(const string&) [with S = char; T = std::basic_string; R = {}; std::string = std::basic_string]':
52:5: warning: control reaches end of non-void function [-Wreturn-type]
Code:
// FFact struct experiments
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// Key-Value data struct
template<typename T>
struct FDataKey
{
FDataKey(string KeyStr, T Data)
{
Key = KeyStr;
Value = Data;
}
string Key;
T Value;
};
// Basic struct to enable generic recursion
template<typename ... T>
struct FFact
{
// GetValue's bottom of the barrel
// ToDo: Implement check that Key was not found
template<typename S>
S GetValue(string Key)
{
S defaultValue;
return defaultValue;
}
};
// Variadic templated container meant for FDataKey<> types only
template<typename T, typename ... R>
struct FFact<T, R...>
{
// Constructors
FFact(){}
FFact(const FDataKey<T>& FirstValue, const auto& ...Rest) : Data(FirstValue), Remainder(Rest...)
{}
// Recursively check Data members for FDataKeys with matching keys
template<typename S>
S GetValue(const string& Key)
{
if(Data.Key == Key)
{
return Data.Value;
}
else
{
return Remainder.GetValue<S>(Key);
}
}
// Member variables
FDataKey<T> Data;
FFact<R...> Remainder;
};
// Run tests
int main()
{
// Setup testing Fact
FFact<char, bool, string> f = FFact<char,bool,string>(
FDataKey<char>("Initial", 'w'),
FDataKey<bool>("Cake",false),
FDataKey<string>("Marco","Polo")
);
cout << f.GetValue<char>("Initial") << endl;
cout << f.GetValue<bool>("Cake") << endl;
cout << f.GetValue<string>("Marco") << endl;
}
1 Answer 1
Here you go, it compiles:
// FFact struct experiments
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <type_traits>
using namespace std;
// Key-Value data struct
template<typename T>
struct FDataKey {
FDataKey(string KeyStr, T Data) {
Key = KeyStr;
Value = Data;
}
string Key;
T Value;
};
// Basic struct to enable generic recursion
template<typename ... T>
struct FFact {
FFact(){}
// GetValue's bottom of the barrel
// ToDo: Implement check that Key was not found
template<typename S>
S GetValue(string Key) {
throw std::runtime_error("key not found");
S defaultValue;
return defaultValue;
}
};
// Variadic templated container meant for FDataKey<> types only
template<typename T, typename ...R>
struct FFact<T, R...> {
FFact(){}
// No need to use auto...
FFact(const FDataKey<T>& FirstValue, const FDataKey<R>&... Rest) :
Data(FirstValue), Remainder(Rest...)
{}
template<typename S>
S GetValue(const string& Key) {
if (Data.Key == Key) {
// you have to statically check
// if Data.Value can be converted to S
// if it can't, then... it can't.
if constexpr (std::is_convertible<T, S>::value) {
return Data.Value;
} else {
throw std::runtime_error("T is not convertiable to S");
}
} else {
// This is the first time I used this syntax, didn't knew about it.
return Remainder.template GetValue<S>(Key);
}
}
// Member variables
FDataKey<T> Data;
FFact<R...> Remainder;
};
// Run tests
int main()
{
// Setup testing Fact
FFact<char, bool, string> f = FFact<char,bool,string>(
FDataKey<char>("Initial", 'w'),
FDataKey<bool>("Cake",false),
FDataKey<string>("Marco","Polo")
);
cout << f.GetValue<char>("Initial") << endl;
cout << f.GetValue<bool>("Cake") << endl;
cout << f.GetValue<string>("Marco") << endl;
}
return Data.Value;- You doreturn Data.Value;from each recursive call, even when you return anS. Ex. inGetValue<char>. C++ is statically typed, eachData.Valuehas to be convertible tocharthen.Data.Valueshould not be returned until the Key matches. Thus, the only type conversion should be S totypeof(Data.Value)which should only throw an error when the incorrect generic types are passed in the call.should not be returnedit will not be returned, but statically all paths have to be valid. Likeint func() { if (0) { return std::string{}; } return 5; }will not compile, no matter string is "never returned".