4

My Problem is the following. I want to sort a list of types based on a list of constexpr values. The problem can be boiled down to this function:

template <typename U, typename V>
auto min(U,V) -> std::conditional_t<U::value < V::value, U, V>
{ return {}; }

whereas value must be some static constexpr member of each type, respecively. The following snippet demonstrates the usage:

// (I)

// This must even be declared outside of a function body due to the statics :(
struct X { static constexpr double value = 2.; };
struct Y { static constexpr double value = 1.; };

int main()
{
    X x;
    Y y;
    auto z = min(x,y);
    std::cout << typeid(z).name() << " : " << z.value << std::endl;
}

My goal is to provide the value as I call the function. The closest thing I got to this goal is the following

template <double (*F)()>
struct Value { static constexpr double value = F(); };

which can be called like this using lambdas:

// (II)
auto w = min(Value<[]{ return 3.14; }>{}, Value<[]{ return 2.71; }>{});
std::cout << typeid(w).name() << " : " << w.value << std::endl;

The actual type to be sorted can be an additional parameter.

The problem is that the above is not valid C++ according to the standard. However, the latest clang does compile this gracefully.

Now, my question is: Is there another standard compliant way to achieve the above (listing (II)), that is, defining a function that computes a type based on constexor objects provided inplace (in some way) as the function argument?


P.S.: I'm aware of the solution using std::integral_constant. This, however, is limited to integral types only. I'm interested in a solution that works for all constexpr objects, in particular floating point types, and strings.

André Bergner
  • 1,429
  • 10
  • 10
  • Out of curiosity: why is this not standard-compliant? – KjMag Jun 27 '17 at 10:04
  • @KjMag See [this question](https://stackoverflow.com/questions/44485610/will-i-be-able-to-declare-constexpr-lambda-inside-a-template-parameter). – Rakete1111 Jun 27 '17 at 10:09

1 Answers1

2

Edit:

To deal with floating point values as well as integral types scenarios you could make use of user defined literal template e.g.:

#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>

template <class FloatingPointType, class... Cs>
constexpr FloatingPointType char_list_to_(Cs... cs) {
    char arr[] = {cs...};
    FloatingPointType lhs = 0;
    bool contains_dot = false;
    for (std::size_t i = 0; i < sizeof...(Cs) && !(contains_dot |= (arr[i] == '.')); i++) { 
        lhs *= 10;
        lhs += arr[i] - '0';
    }
    FloatingPointType rhs = 0;
    for (int i = sizeof...(Cs) - 1; i > 0 && arr[i] != '.'; i--) {
       rhs /= 10;
       rhs += arr[i] - '0';
    }
    rhs /= 10;
    return (contains_dot)?lhs+rhs:lhs;
}

template <class FloatingPointType, char... Cs>
struct FloatingPointValue {

    static constexpr FloatingPointType value = char_list_to_<FloatingPointType>(Cs...);

    constexpr operator FloatingPointType() {
        return value;
    }
};

template <class FloatingPointType, char... Cs>
constexpr FloatingPointType FloatingPointValue<FloatingPointType, Cs...>::value;

template <char... Cs>
FloatingPointValue<double, Cs...> operator""_fv() {
    return {};
}


template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }

int main() {
   auto w = min(3.14_fv, 2.71_fv);
   std::cout << typeid(w).name() << " : " << w.value << std::endl;
}

Output:

18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE : 2.71

Output of c++filt -t 18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE:

FloatingPointValue<double, (char)50, (char)46, (char)55, (char)49>

[live demo]


But if you wish to apply the same to string literal there is currently a lack of support of the feature caused by a c++ standard. There is however a gnu extension supported by clang and gcc if you are capable to accept less portable option:

#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>

template <class CharT, CharT... Cs>
struct Value {

    static constexpr std::size_t size = sizeof...(Cs);
    static constexpr CharT const value[sizeof...(Cs) + 1] = {Cs..., '\0'};

    template <class RHS>
    constexpr bool operator<(RHS) {
        for (std::size_t i = 0; i < size && i < RHS::size; i++) {
            if (value[i] != RHS::value[i]) {
                return value[i] < RHS::value[i];
            }
        }
        return size < RHS::size;
    }
};

template <class CharT, CharT... Cs>
constexpr CharT const Value<CharT, Cs...>::value[sizeof...(Cs) + 1];

template <class CharT, CharT... Cs>
Value<CharT, Cs...> operator""_v() {
    return {};
}


template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }

int main() {
   auto w = min("cde"_v, "abc"_v);
   std::cout << typeid(w).name() << " : " << w.value << std::endl;
}

Output:

5ValueIcJLc97ELc98ELc99EEE : abc

Output of c++filt -t 5ValueIcJLc97ELc98ELc99EEE:

Value<char, (char)97, (char)98, (char)99>

[live demo]

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • That's a nice solution for strings, indeed. Unfortunately it's limited to gcc and I don't see how this can be extended to floating point values. Here the string is passed as variadic template list of chars, but I cannot do this for floating point. I would end up in the same situation I'm trying to solve – hiding the floats in some static member. – André Bergner Jun 27 '17 at 14:25
  • @AndréBergner Yep you would need to store the floating point in some static member yet you would create it inline e.g. by the call: `max("3.14"_vf, "2.71"_vf)`. The effort would boil down to overload less operator of the Value type. PS. clang also supports it... And there are proposals to standardise it. Unfortunately you won't get it in c++17 but maybe c++20... who knows?! I'm crossing my fingers on it :) – W.F. Jun 27 '17 at 14:33
  • @AndréBergner actually it turns out that there is already standardised user defined literal template for numeric values (see e.g. [here](http://en.cppreference.com/w/cpp/language/user_literal)) This would make the approach even more portable e.g. `max(3.14_vf, 2.71_vf)` – W.F. Jun 27 '17 at 15:49
  • I totally forgot that one. Yes, that solves the floating point case. What do I have to do to get clang compile the string one? Do I need a special command line switch? Does not work with my latest clang. Still the general case for arbitrary constexpr types would be nice to solve. – André Bergner Jun 27 '17 at 20:46
  • @AndréBergner Not sure. The newest clang on wandbox haven't got any problem here. Which version causes the issue? – W.F. Jun 27 '17 at 21:10
  • @AndréBergner yep clang got some issues with static constexpr array here until version 3.8 (at least on wandbox). I think it can be workaround thought e.g. by using of tuple of integral constants instead. I'll try to scratch some code tomorrow. – W.F. Jun 27 '17 at 21:18
  • @AndréBergner edited the post. Now you should make it compile also in older clangs. Also added the floating point user defined literal templates. – W.F. Jun 28 '17 at 09:54