Vincent
Vincent

Reputation: 60421

Constant expression from the choice of overloaded function?

Considering the following example (I posted it for several different questions today) :

#include <iostream>
#include <vector>
#include <array>
#include <type_traits>

// Version A
template<typename T>
constexpr unsigned int f(const T&)
{
    return 1;
}

// Version B
template<typename... T1, template<typename...> class T>
constexpr unsigned int f(const T<T1...>&)
{
    return 2;
}

// Version C
template<typename T1, template<typename, unsigned int...> class T, unsigned int... N>
constexpr unsigned int f(const T<T1, N...>&)
{
    return 3;
}

// Main
int main(int argc, char* argv[])
{
    std::integral_constant<int, f(double())> a;
    std::integral_constant<int, f(std::vector<double>())> b;
    std::integral_constant<int, f(std::array<double, 3>())> c;
    std::cout<<a<<b<<c<<std::endl; // The goal is to return 123
    return 0;
}

This code does not compile and returns the following compilation error :

temporary of non-literal type 'std::vector<double>' in a constant expression

How to modify this code in order to compile it ?

Note : the goal is to convert a type that will be taken by the first version of the function to 1, a type that will be taken by the second version of the function to 2, etc...

Upvotes: 3

Views: 6852

Answers (4)

Drax
Drax

Reputation: 13288

You need partial specialization wich is impossible on functions, wrapping them in a struct/class gets the job done :

#include <iostream>
#include <vector>
#include <array>
#include <type_traits>

// Version A                                                                                                                                                   
template<typename T>
struct f
{
  constexpr static unsigned int execute()                                                                                                                      
  {                                                                                                                                                            
    return 1;                                                                                                                                                  
  }
};

// Version B                                                                                                                                                   
template<template <typename ... > class Tpl, typename ... TplArgs>
struct f< Tpl<TplArgs...> >
{
  constexpr static unsigned int execute()                                                                                                                      
  {                                                                                                                                                            
    return 2;                                                                                                                                                  
  }
};

// Version C                                                                                                                                                   
template<template<typename, std::size_t...> class Tpl, typename FirstArg, std::size_t... N>
struct f< Tpl<FirstArg, N...> >
{
  constexpr static unsigned int execute()                                                                                                                      
  {                                                                                                                                                            
    return 3;                                                                                                                                                  
  }
};

// Main                                                                                                                                                        
int main(int argc, char* argv[])                                                                                                                               
{                                                                                                                                                              
  std::integral_constant<int, f<double>::execute()> a;                                                                                                         
  std::integral_constant<int, f<std::vector<double>>::execute()> b;                                                                                            
  std::integral_constant<int, f<std::array<double, 3>>::execute()> c;                                                                                          

  std::cout << a << ' ' << b << ' ' << c << std::endl;

  return 0;
}                                                                                                        

Upvotes: 2

Vincent
Vincent

Reputation: 60421

I found the solution by using pointers instead of references :

// Version A
template<typename T>
constexpr unsigned int f(const T*)
{
    return 1;
}

// Version B
template<typename... T1, template<typename...> class T>
constexpr unsigned int f(const T<T1...>*)
{
    return 2;
}

// Version C
template<typename T1, template<typename, unsigned int...> class T, unsigned int... N>
constexpr unsigned int f(const T<T1, N...>*)
{
    return 3;
}

// Main
int main(int argc, char* argv[])
{
    std::vector<double> tmp;
    std::integral_constant<int, f(static_cast<double*>(nullptr))> a;
    std::integral_constant<int, f(static_cast<decltype(tmp)*>(nullptr))> b;
    std::integral_constant<int, f(static_cast<std::array<double, 3>*>(nullptr)) > c;
    std::cout<<a<<b<<c<<std::endl;
    return 0;
}

Maybe it's not the most elegant way to do it but it works. If someone has an elegant equivalent of this, I am very interested in.

Upvotes: 0

Lol4t0
Lol4t0

Reputation: 12547

You cannot use temporary vector when defining constant:

int main(int argc, char* argv[])
{
    std::integral_constant<int, f(double())> a;
    std::vector<double> vec;
    std::integral_constant<int, f(vec)> b;
    std::integral_constant<int, f(std::array<double, 3>())> c;

    std::cout << a << b << c;
    return 0;
}

The thing is that compiler could possibly omit vector creation, if vector's only purpose was passing to the constant expression function, but actually it cannot, because vector is not literal type.

std::array is only a thing wrapper above c array, it has trivial constructor and destructor. As double is also literal type, array of doubles becomes literal.

Note, however, that if you define

struct A
{
    A(){std::cout << "I'm so complicated A!\n"; }
}

you would not be able to use constructs:

int main(int argc, char* argv[])
{
    std::integral_constant<int, f(A())> a;
    std::integral_constant<int, f(std::array<A, 3>())> c;

    std::cout << a << b << c;
    return 0;
}

either, while

int main(int argc, char* argv[])
{
    A a_v;
    std::integral_constant<int, f(a_v)> a;

    std::array<A, 3> c_v
    std::integral_constant<int, f(c_v)> c;

    std::cout << a << b << c;
    return 0;
}

still would be possible.

Upvotes: 1

Andrzej
Andrzej

Reputation: 5127

The question to you is how much can we afford to modify this code? For instance, the following compiles, but is it what you were trying to do?

#include <iostream>
#include <vector>
#include <array>
#include <type_traits>

// Version A
template<typename T>
constexpr unsigned int f()
{
    return 1;
}

// Version B
template<typename... T1, template<typename...> class T>
constexpr unsigned int f()
{
    return 2;
}

// Version C
template<typename T1 = double, template<typename, unsigned int...> class T, unsigned int... N>
constexpr unsigned int f()
{
    return 3;
}

// Main
int main(int argc, char* argv[])
{
    std::integral_constant<int, f<double>()> a;
    std::integral_constant<int, f<std::vector<double>>()> b;
    std::integral_constant<int, f<std::array<double, 3>>()> c;
    return 0;
}

Upvotes: 0

Related Questions