Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can constexpr-if-else bodies return different types in constexpr auto function?

I'm trying to write a function that maps an enumeration of values to a set of types based on the runtime value of the enumeration. I realize that you cannot return different types based on the runtime value of an enumeration because the compiler wouldn't know how much stack space to allocate. However I'm trying to write this as a constexpr function, using the new if-constexpr functionality to implement this.

I'm getting an error from clang complaining that I'm using an illegally specified template parameter. Does anyone see how to implement this?

edit: Here is an easier to grok version demonstrating my problem more concisely: http://coliru.stacked-crooked.com/a/2b9fef340bd167a8

old code:

#include <cassert>
#include <tuple>
#include <type_traits>

namespace
{

enum class shape_type : std::size_t
{
  TRIANGLE = 0u,
  RECTANGLE,
  POLYGON,
  CUBE,
  INVALID_SHAPE_TYPE
};

template<std::size_t T>
struct shape {
};

using triangle = shape<static_cast<std::size_t>(shape_type::TRIANGLE)>;
using rectangle = shape<static_cast<std::size_t>(shape_type::RECTANGLE)>;
using polygon = shape<static_cast<std::size_t>(shape_type::POLYGON)>;
using cube = shape<static_cast<std::size_t>(shape_type::CUBE)>;

template<std::size_t A, std::size_t B>
static bool constexpr same() noexcept { return A == B; }

template<std::size_t ST>
static auto constexpr make_impl(draw_mode const dm)
{
  if constexpr (same<ST, shape_type::TRIANGLE>()) {
    return triangle{};
  } else if (same<ST, shape_type::RECTANGLE>()) {
    return rectangle{};
  } else if (same<ST, shape_type::POLYGON>()) {
    return polygon{};
  } else if (same<ST, shape_type::CUBE>()) {
    return cube{};
  } else {
    assert(0 == 5);
  }
}

static auto constexpr make(shape_type const st, draw_mode const dm)
{
  switch (st) {
      case shape_type::TRIANGLE:
        return make_impl<shape_type::TRIANGLE>(dm);
      case shape_type::RECTANGLE:
        return make_impl<shape_type::RECTANGLE>(dm);
      case shape_type::POLYGON:
        return make_impl<shape_type::POLYGON>(dm);
      case shape_type::CUBE:
        return make_impl<shape_type::CUBE>(dm);
      case shape_type::INVALID_SHAPE_TYPE:
        assert(0 == 17);
  }
}

} // ns anon

////////////////////////////////////////////////////////////////////////////////////////////////////
// demo
int main()
{
}

Errors:

/home/benjamin/github/BoomHS/main.cxx:42:6: warning: constexpr if is a
C++1z extension [-Wc++1z-extensions]

if constexpr (same<ST, shape_type::TRIANGLE>()) {
        ^ /home/benjamin/github/BoomHS/main.cxx:59:16: error: no matching function for call to 'make_impl'
        return make_impl<shape_type::TRIANGLE>(dm);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/benjamin/github/BoomHS/main.cxx:40:23: note: candidate template
ignored: invalid explicitly-specified argument for template parameter
'ST' static auto constexpr make_impl(draw_mode const dm)
                      ^ /home/benjamin/github/BoomHS/main.cxx:61:16: error: no matching function for call to 'make_impl'
        return make_impl<shape_type::RECTANGLE>(dm);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/benjamin/github/BoomHS/main.cxx:40:23: note: candidate template
ignored: invalid explicitly-specified argument for template parameter
'ST' static auto constexpr make_impl(draw_mode const dm)
                      ^ /home/benjamin/github/BoomHS/main.cxx:63:16: error: no matching function for call to 'make_impl'
        return make_impl<shape_type::POLYGON>(dm);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/benjamin/github/BoomHS/main.cxx:40:23: note: candidate template
ignored: invalid explicitly-specified argument for template parameter
'ST' static auto constexpr make_impl(draw_mode const dm)
                      ^ /home/benjamin/github/BoomHS/main.cxx:65:16: error: no matching function for call to 'make_impl'
        return make_impl<shape_type::CUBE>(dm);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/benjamin/github/BoomHS/main.cxx:40:23: note: candidate template
ignored: invalid explicitly-specified argument for template parameter
'ST' static auto constexpr make_impl(draw_mode const dm)
like image 890
Short Avatar asked Dec 21 '16 22:12

Short


People also ask

Can a constexpr function use if?

constexpr if can be used to replace several tricks that were already done: SFINAE technique to remove not matching function overrides from the overload set. you might want to look at places with C++14's std::enable_if - that should be easily replaced by constexpr if . Tag dispatch.

Can constexpr be modified?

A template metaprogram runs at compile, but a constexpr function can run at compile time or runtime. Arguments of a template metaprogram can be types, non-types such as int , or templates. There is no state at compile time and, therefore, no modification.

Are constexpr functions inline?

A constexpr specifier used in a function or static data member (since C++17) declaration implies inline .

Does constexpr need to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?


1 Answers

Yes, what you are attempting is allowed. The two relevant excerpts I've found in N4606 are:

6.4.1/2 [stmt.if]

If the if statement is of the form if constexpr [...] If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement.

Indicating an untaken branch in a constexpr if is a discarded statment. Further, auto functions only consider non-discarded return statements for deducing the return type

7.1.7.4/2 [dcl.spec.auto] (emphasis mine)

[...] If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (6.4.1).

Simplified, the following code works on both gcc and clang head.

namespace {

enum class shape_type { TRIANGLE, RECTANGLE, CIRCLE};

template<shape_type>
struct shape { };

using triangle = shape<shape_type::TRIANGLE>;
using rectangle = shape<shape_type::RECTANGLE>;
using circle = shape<shape_type::CIRCLE>;

template<shape_type ST>
constexpr auto make() {
  if constexpr (ST == shape_type::TRIANGLE) {
    return triangle{};
  } else if constexpr (ST == shape_type::RECTANGLE) {
    return rectangle{};
  } else if constexpr (ST == shape_type::CIRCLE) {
    return circle{};
  } 
}

}

int main() {
  auto t = make<shape_type::TRIANGLE>();
  auto r = make<shape_type::RECTANGLE>();
  auto c = make<shape_type::CIRCLE>();
}
like image 145
Ryan Haining Avatar answered Sep 30 '22 03:09

Ryan Haining