Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ignore template parameter in type matching

This is my current program:

#include <type_traits>

template<class Feature, class... FeatureList>
struct has_feature {
    static constexpr bool value = (std::is_same_v<Feature, FeatureList> || ...);
};

template<class Feature, class... FeatureList>
inline constexpr bool has_feature_v = has_feature<Feature, FeatureList...>::value;

template<class Feature, class ...FeatureList>
static constexpr bool isConfiguredWith() {
    return has_feature_v<Feature, FeatureList...>;
}

struct CanWalk {
};

struct CanNotWalk {
};

template<class... FeatureList>
struct Robot {
    static auto configure() {
        return Robot<WalkFeature < FeatureList...>>
        ();
    }

private:
    template<typename ...Config>
    using WalkFeature =
    std::conditional_t<isConfiguredWith<CanWalk, Config...>(), CanWalk, CanNotWalk>;
};

int main() {
    Robot<CanWalk> robot_A = Robot<CanWalk>::configure();
    Robot<CanNotWalk> robot_B = Robot<>::configure();

    return 0;
}

Basically, Robot is a struct that can be configured with many other struct (they are used as token here), then Robot<T...>::configure() trim and organize the template parameters passed into to Robot. In the end we have:

    Robot<CanWalk> robot_A = Robot<CanWalk, CanWalk>::configure();
    Robot<CanNotWalk> robot_B = Robot<>::configure();

Although duplicated features CanWalk are passed into as template parameters, they are all deleted when constructing Robot via function configure().

This is working well until I add a template parameter to feature CanWalk:

template <int Speed>
struct CanWalk {
};

Now everything breaks since CanWalk is no longer a legit type, it needs a template parameter.

For error error: use of class template 'CanWalk' requires template arguments occured from:

template<typename ...Config>
using WalkFeature =
std::conditional_t<isConfiguredWith<CanWalk>(), CanWalk, CanNotWalk>;

How do I fix it?

How can I define them as:

Robot<CanWalk<5>> robot_A = Robot<CanWalk<5>>::configure();
Robot<CanNotWalk> robot_B = Robot<>::configure();

?

live code: https://godbolt.org/z/4K9TxohWq

like image 270
Rahn Avatar asked Nov 23 '25 11:11

Rahn


2 Answers

I see three challenges here:

  1. you need to extract a specific trait (CanWalk), regardless of its parameters, from a parameter pack
  2. you need to ignore extra copies of the same trait
  3. you need to set a default trait (CanNotWalk) if that trait is not present

I don't know a better way to do this than recursively:

// definition
template<template<auto...> class FeatureType, class DefaultFeature, class... FeatureList>
struct find_feature;

// next trait matches, stop recursing and return it
template<template<auto...> class FeatureType, class DefaultFeature, auto... Param, class... RemainingFeatures>
struct find_feature<FeatureType, DefaultFeature, FeatureType<Param...>, RemainingFeatures...> {
    using type = FeatureType<Param...>;
};

// next trait does not match, skip by inheriting from rest of list
template<template<auto...> class FeatureType, class DefaultFeature, class FirstFeature, class... RemainingFeatures>
struct find_feature<FeatureType, DefaultFeature, FirstFeature, RemainingFeatures...> 
    : find_feature<FeatureType, DefaultFeature, RemainingFeatures...> { };

// no more traits, return default trait
template<template<auto...> class FeatureType, class DefaultFeature>
struct find_feature<FeatureType, DefaultFeature> {
    using type = DefaultFeature;
};

// alias
template<template<auto...> class FeatureType, class... FeatureList>
using find_feature_t = typename find_feature<FeatureType, FeatureList...>::type;

this should work for any feature that is a class template and any number of non-type parameters.

usage:

template <int speed>
struct CanWalk {};
struct CanNotWalk {};

template<class... FeatureList>
struct Robot {
    static auto configure() {
        return Robot<WalkFeature < FeatureList...>>
        ();
    }

private:
    template<typename ...Config>
    using WalkFeature = find_feature_t<CanWalk, CanNotWalk, Config...>;
};

auto robot_A = Robot<CanWalk<42>, CanWalk<25>>::configure();
static_assert(std::is_same_v<decltype(robot_A), Robot<CanWalk<42>>>, "");

auto robot_B = Robot<>::configure();
static_assert(std::is_same_v<decltype(robot_B), Robot<CanNotWalk>>, "");
like image 172
parktomatomi Avatar answered Nov 26 '25 01:11

parktomatomi


Removing some genericity, you might do something like:

template <typename T> struct Tag { using type = T; };

template <std::size_t> struct CanWalk {};
struct CanNotWalk {};

template <std::size_t N> Tag<CanWalk<N>> has_walk(Tag<CanWalk<N>>); // No impl
template <typename T> Tag<CanNotWalk> has_walk(Tag<T>); // No impl

template<class... FeatureList>
struct Robot {
    static auto configure() {
        struct all_features : Tag<FeatureList>..., Tag<struct Empty> {};
        return Robot<typename decltype(has_walk(all_features{}))::type>{};
    }
};

int main() {
    [[maybe_unused]] Robot<CanWalk<42>> robot_A = Robot<CanWalk<42>>::configure();
    [[maybe_unused]] Robot<CanNotWalk> robot_B = Robot<>::configure();
}

Demo

That simple way doesn't handle duplicates, but can with some extra work (idea is to have struct all_features : Tag<Ts, Is>... with std::index_sequence)

like image 20
Jarod42 Avatar answered Nov 26 '25 00:11

Jarod42