2
votes

I am trying to use 'decltype' on a variadic function template to get its return value type and then use it to define a member variable. But I keep getting this error:

D:\Qt Projects\OpenGL_PhysicsSim\lib\Physics Effects\include\ParticleList.hpp:89: error: cannot convert 'std::tuple<std::uniform_real_distribution<double>, std::uniform_real_distribution<double>, std::uniform_real_distribution<double> >' to 'int' in assignment
     distributionTuple = createDistribution<Type, Types...>(meanValue, meanValues..., varianceValue, varianceValues...);

Basically, the decltype fails and declare distributionTuple as int rather than deduce return type of createDistribution.

template<typename AttributeType, typename Type, typename ...Types>
class ParticleAttributeGenerator
{
private:
 template<typename T>
 auto createDistribution(T meanValue, T varianceValue)
 {
   static_assert(
     std::is_integral<Type>::value || std::is_floating_point<Type>::value,
     "Type should be either integral value or floating point value");

   using distType = typename std::conditional< std::is_integral<T>::value,
                                               std::uniform_int_distribution<>,
                                               std::uniform_real_distribution<> >::type;
   T a = meanValue - varianceValue;
   T b = meanValue + varianceValue;

   return std::tuple<distType>(distType(a,b));
 }

 template<typename Tfirst, typename ...Trest>
 auto createDistribution(Tfirst meanValue, Trest... meanValues, Tfirst varianceValue, Trest... varianceValues)
 {
   static_assert(
     std::is_integral<Type>::value || std::is_floating_point<Type>::value,
     "Type should be either integral value or floating point value");

   using distType = typename std::conditional< std::is_integral<Tfirst>::value,
                                               std::uniform_int_distribution<>,
                                               std::uniform_real_distribution<> >::type;
   Tfirst a = meanValue - varianceValue;
   Tfirst b = meanValue + varianceValue;

   static_assert((sizeof...(meanValues)) == (sizeof...(varianceValues)), "number of meanValues and varianceValues should match!");

   return std::tuple_cat(std::tuple<distType>(distType(a,b)), createDistribution<Trest...>(meanValues..., varianceValues...));
 }

public:
 ParticleAttributeGenerator(Type meanValue, Types... meanValues, Type varianceValue, Types... varianceValues)
 {
   distributionTuple = createDistribution<Type, Types...>(meanValue, meanValues..., varianceValue, varianceValues...);  // 89 : error          
 }

private:
  //using result_type_t = typename std::result_of<createDistribution(Type, Types..., Type, Types...)>::type;
  decltype (createDistribution<Type, Types...>(Type, Types..., Type, Types...)) distributionTuple;
  //decltype (createDistribution<Type, Types...>(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)) distributionTuple;
};

It works when I provide all the values of arguments of createDistribution, but that is not the behavior I was looking for. Since I don't know how many parameters there will be for that function, it has to stay as variadic template function.

Example of how I plan to use the class template:

ParticleAttributeGenerator<glm::vec3, float, float, float> example1(3.0f, 1.0f, 3.0f, 1.0f, 3.0f, 1.0f);
ParticleAttributeGenerator<glm::u8vec4, uint8_t, uint8_t, uint8_t, uint8_t> example2(3, 2, 25, 5, 51, 12, 32, 3);

if I don't have any member variable and use distributedTuple as:

auto distributionTuple = createDistribution<Type, Types...>(meanValue, meanValues..., varianceValue, varianceValues...);

It compiles, therefore I believe createDistribution manages to define tuple recursively. But that is no use for me. There must be something wrong that I am missing. I use GCC 4.9.2 in -std=c++14 mode.

2

2 Answers

4
votes

First off, let's massively simplify the example down to something more manageable with less external stuff:

template <typename T>
class Bar
{
private:
    template <typename U>
    auto foo(U a, U b)
    {
        return std::tuple<U>(a+b);
    }

public:
    Bar(T a, T b)
    {
        distributionTuple = foo<T>(a, b);
    }

private:
    decltype (foo<T>(T, T)) distributionTuple;
};

int main()
{
    Bar<int> b(4, 4);
}

This gives me the sample compile error that you present in your question (cannot convert std::tuple<int> to int. That's because:

foo<T>(T, T)

isn't a valid function call. You need to call foo<T> with expressions that have those types. Not just a list of types. For that, we have std::declval. That is:

foo<T>(std::declval<T>(), std::declval<T>())

Once you make that change, you'll get a new compile error: "cannot call member function foo without object." So let's add an object with declval again. The following compiles:

template <typename T>
class Bar
{
private:
    template <typename U>
    auto foo(U a, U b)
    {
        return std::tuple<U>(a+b);
    }

public:
    Bar(T a, T b)
    {
        distributionTuple = foo<T>(a, b);
    }

private:
    decltype(std::declval<Bar>().foo<T>(
                 std::declval<T>(), 
                 std::declval<T>())
             ) distributionTuple;
};
1
votes

Use std::declval when you need to construct a complete invocation of a function in an unevaluated context. Using it, you would declare distributionTuple like this:

decltype (std::declval<ParticleAttributeGenerator>().createDistribution<Type, Types...>(std::declval<Type>(), std::declval<Types>()..., std::declval<Type>(), std::declval<Types>()...)) distributionTuple;