How to do a constexpr count based on a type trait

1.2k views Asked by At

I have a database of objects and would like to count how many are of a particular type at compile time, but I'm having a bit of trouble getting this to compile.

Here's a cut down example of what I've been trying so far, but this fails to compile with "error: call to function 'do_count' that is neither visible in the template definition nor found by argument-dependent lookup"

Is there a better way?

#include <cstdint>
#include <type_traits>
#include <cstddef>

struct unused_tag {};
struct used_tag {};

template<std::size_t X>
struct traits {
    using type = unused_tag;
};

template<>
struct traits<7> {
    using type = used_tag;
};

static constexpr const std::size_t MAX_X = 10;

template<std::size_t X = 0>
constexpr
std::enable_if_t<
    !std::is_same<typename traits<X>::type, unused_tag>::value,
    std::size_t>
do_count()
{
    return do_count<X + 1>() + 1;
}

template<std::size_t X = 0>
constexpr
std::enable_if_t<
    std::is_same<typename traits<X>::type, unused_tag>::value,
    std::size_t>
do_count()
{
    return do_count<X + 1>();
}

template<>
constexpr std::size_t do_count<MAX_X>()
{
    return 0;
}

static constexpr const std::size_t COUNT = do_count();
2

There are 2 answers

0
Lawrence On BEST ANSWER

It seems like you found a different solution to your problem but here is a solution using std::enable_if if you were curious.

The problem is that the call of do_count from the !std::is_same<...> version cannot see the std::is_same<...> version so, as your compiler said, it is not visible from the call site and can't be resolved. To fix this, just make a forward declaration of the std::is_same<...>.

For your example, the following compiles for me:

#include <cstdint>
#include <type_traits>
#include <cstddef>

struct unused_tag {};
struct used_tag {};

template<std::size_t X>
struct traits {
    using type = unused_tag;
};

template<>
struct traits<9> {
    using type = used_tag;
};

static constexpr const std::size_t MAX_X = 10;

//
// forward declaration
//
template<std::size_t X = 0>
constexpr
std::enable_if_t<
    std::is_same<typename traits<X>::type, unused_tag>::value,
    std::size_t>
do_count();

template<std::size_t X = 0>
constexpr
std::enable_if_t<
    !std::is_same<typename traits<X>::type, unused_tag>::value,
    std::size_t>
do_count()
{
    return do_count<X + 1>() + 1;
}

template<std::size_t X>
constexpr
std::enable_if_t<
    std::is_same<typename traits<X>::type, unused_tag>::value,
    std::size_t>
do_count()
{
    return do_count<X + 1>();
}

template<>
constexpr std::size_t do_count<MAX_X>()
{
    return 0;
}

static constexpr const std::size_t COUNT = do_count();
0
Patrick_O On

Turns out I was being way too smart for my own good, it's as simple as this:

#include <cstdint>
#include <type_traits>
#include <cstddef>

struct unused_tag {};
struct used_tag {};

template<std::size_t X>
struct traits {
    using type = unused_tag;
};

template<>
struct traits<7> {
    using type = used_tag;
};

static constexpr const std::size_t MAX_COUNT = 10;

template<std::size_t X = 0>
constexpr std::size_t do_count()
{
    if (std::is_same<typename traits<X>::type, unused_tag>::value)
        return 0 + do_count<X + 1>();
    return 1 + do_count<X + 1>();
}

template<>
constexpr std::size_t do_count<MAX_COUNT>()
{
    return 0;
}

static constexpr const std::size_t COUNT = do_count();

static_assert(COUNT == 1);