LazarusOfSuburbia
LazarusOfSuburbia

Reputation: 67

Accessor function member for variadic templated struct

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;
}

Upvotes: 0

Views: 154

Answers (1)

KamilCuk
KamilCuk

Reputation: 141040

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;
}

Upvotes: 1

Related Questions