Constructor gets ugly with `std::function`

So I'm refactoring an old class which essentially runs some functions in a pipeline and for the premise of the question, this particular class accepts functors as parameters in the constructors.

I have the following 3 options, ranked in order of how I like them:

  1. Use a trait class instead to squirrel types away and to consolidate all the different types in one place. Requires a using declaration inside the class to be really useful, and it could use some MACRO to clean it up and look real nice.
  2. If I put the std::function in the place, it makes the code look ugly. I don't think it is acceptable, but it's scalable.
  3. If I use global using directives, it pollutes the namespace with what looks even uglier due to templates. But at least the constructors look nice. However, now it's an effort to grok for the type of the functor that ctor accepts. And it's not very flexible, unless I use option 1, in which case I might as well go all in on option 1.

Which is the preferred alternative? Do you have a better option? (And no, using #define is not an option). Any other ways I can refactor it to make my life in 2 weeks a little less hell?

Please keep in mind that there is a fair mix of CRTP thrown in the real code for fun (and speed) which makes it weird with these template parameters going round and round.

Drum-roll for the code please

namespace cpp_questions {
class dummy;

// assume class to be specialized here, declared somewhere else
template<>
class dummy_traits<dummy, class = concept_checker_dont_worry_about_it<dummy>> {
    using map = wow::secret_map;
    using input_details = wow::secret_struct;
    using factory_functor = std::function<typename map::mapped_type(const std::string&, 
                                                                    std::function<void(void)>,
                                                                    const input_details&,
                                                                    const std::string&)>;
};  // the death is upon us with class dummy_traits<dummy, _>

template <class T>
using checking_functor = std::function<bool(const complicated_type_like_array_of_vector<typename dummy_traits<T>::map::mapped_type,
                                                                                        wow::secret_enum::__MAX__>&)>;

struct dummy {
    using input_details = typename dummy_traits<dummy>::input_details;

    dummy(// option 1: traits class
          typename dummy_traits<dummy>::factory_functor generateElements,
          // option 2: here (chosen a simpler type)
          std::function<input_details(input_details, const input_details&)> foldItBaby,
          // option 3: worst, because now I can't overload it if I'm not using a traits class
          checking_functor overrideCheck = [](auto& input) -> bool {
                  return input[LETS_SAY_THIS_IS_COMPILE_TIME_DEFAULT].size();
              }) {
        // let's do boring stuff here
    }
};  // the death is upon us with class dummy
} // all hail the end of namespace

NINJA-EDIT: code indent fix

3 comments · 50% upvoted · other discussions

comments

OmegaNaughtEquals1

Assuming dummy doesn't need to store the function object, then I would make it a template class and use static_assert to enforce the parameter type. If dummy_traits is usable beyond dummy, then CRTP would be fine. Otherwise, it's overkill since we know that dummy satisfies the trait conditions. I generally prefer traits classes for enforcing type checks, so that's what I would use.

template<>
template<typename T>
class dummy_traits<dummy<T>, class = concept_checker_dont_worry_about_it<dummy<T>>> {
    using map = wow::secret_map;
    using input_details = wow::secret_struct;
    using factory_functor = std::function<typename map::mapped_type(const std::string&,
                                                                    std::function<void(void)>,
                                                                    const input_details&,
                                                                    const std::string&)>;
};

template <typename GeneratorFunc>
struct dummy {
    dummy(GeneratorFunc f) {
        static_assert(
            std::is_convertible<
                GeneratorFunc,
                typename dummy_traits<GeneratorFunc>::factory_functor
            >::value,
            "Bad functor signature!");
    }
};
codingate [OP]

Thanks, that helps :)

Though I'd have a template parameter explosion, it's manageable and makes the rest of the code so much better.

Minor correction for anyone who comes by later. It's not template <> template <typename T>, it's template <typename T>.

Cheers, Here's to moving from C99 to C++17

OmegaNaughtEquals1

Minor correction for anyone who comes by later. It's not template <> template <typename T>, it's template <typename T>.

Whoops. Yes. That's what I get for not running it through a compiler first.

Cheers, Here's to moving from C99 to C++17

That's a very good transition!