Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRTP: Traits #84

Merged
merged 2 commits into from
Mar 11, 2019
Merged

CRTP: Traits #84

merged 2 commits into from
Mar 11, 2019

Conversation

akleeman
Copy link
Collaborator

@akleeman akleeman commented Mar 7, 2019

This change breaks up traits.h into several application specific traits files: core/traits.h, covariance_functions/traits.h and cereal/traits.h. And breaks up test_traits.cc respectively. The bulk of this change is a result of the moves and adding tests, but core/traits.h has changed substantially.

Namely, I've added the following traits: first_template_param_is_base_of, is_valid_fit_type, has_valid_fit, has_possible_fit, fit_model_type, fit_type and has_valid_predict.

These are already being used in ModelBase, FitModel and Predictions, look there for example usage.

Copy link
Contributor

@jbangelo jbangelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general looks fine. I would strongly suggest picking a style to follow in your type traits, right now they are all a bit different in style which makes it difficult to read.

*/
template <class T> struct delay_static_assert : std::false_type {};
template <typename X> class is_complete {
Copy link
Contributor

@jbangelo jbangelo Mar 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this style of type traits, using partial template specialization:

template<typename T, typename = void>
struct is_complete : std::false_type { };

template<typename T>
struct is_complete<T, typename std::enable_if<sizeof(T) != 0>::type> : std::true_type { };

It's the style used in most of Boost's type trait library, and is easier to read to my eye.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I see what you're saying. For a lot of these traits (particularly the first ones I wrote) I started with something I found in cereal, Eigen or stackoverflow then beat on them until they worked for our case ... suppose that explains the mish mash of styles. I'll give converting to the Boost style a shot.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbangelo I started to switch the style ... then found I'm not super proficient with the boost style and decided it was turning into more effort than it's worth. If you want to take a stab at it feel free. Meanwhile I added an issue: #86

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative would be to change the boost-ish style traits to be more inline with the rest of what albatross does. Consistency in style is important, the actual style doesn't really matter. 😃

Don't take this the wrong way. You already have to bend your mind a bit to grok albatross, using a bit different style in your traits isn't going to make it that much more difficult.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'm with you. I was able to get everything except fit_type to follow the non-boost style. If you can think of a way to switch that over I'm happy to go that route temporarily.

static constexpr bool value = decltype(test<X>(0))::value;
template <typename ModelType, typename FeatureType, int = 0>
struct fit_type {
typedef void type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the FeatureType template parameter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the third specialization of fit_type requires it. It also sort of makes sense for the application since we're asking which fit type would be returned for a model fit with a certain feature type.

std::string get_name() {
static_assert(std::is_same<DummyType, Derived>::value,
"never do covariance_function.get_name<T>()");
return typeid(Derived).name();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI using typeid isn't a compile time thing, it'll interrogate the object at run time. Using RTTI has additional impacts on the size of the compiled code. I'm not saying it shouldn't be used, just wanting to make sure you're aware of the implications. It's probably my embedded background that makes this stand out to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think I'm ok with that in this case since these methods really aren't used much, more for convenience when debugging. If it concerns you having it in here though I'm happy to remove the default get_name() which uses typeid and force a user implementing a model to define std::string name() const.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason to get rid of it. In the past I've usually disabled RTTI, but that's because the projects have been trying to shave off every last byte they could from the executable. I don't think we're anywhere near needing to shrink the executable size in Orion.

struct MultiInheritCallImpl : public U, public BaseWithPublicCallImpl {};
}

template <typename U> class has_any_call_impl {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really clever way to check for private call_impl_ as well!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, yeah a while back I got bit pretty hard with name hiding ... then realized it could be useful.

}

TEST(test_traits_core, test_fit_type) {
EXPECT_TRUE(bool(std::is_same<Z, fit_type<FitModel<X, Z>, Y>::type>::value));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some failure cases would be good here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently calling fit_type<Model, Feature> for a model which doesn't have a valid fit for that feature results in a compile error which, I think, is desirable but makes testing failure modes difficult.

Speaking of which, are you aware of any ways of unit testing for situations that you want to make sure don't compile?

TEST(test_traits_core, test_adaptable_fit_type) {
EXPECT_TRUE(bool(std::is_base_of<Fit<Adaptable<Extended>, X>,
fit_type<Extended, Y>::type>::value));
EXPECT_TRUE(bool(std::is_base_of<Fit<Adaptable<Extended>, X>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some failure cases would be good here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above: I can't set up failure cases for these since inspecting something without a fit_type gives compile error. I think that's OK though.

bool(has_valid_predict_marginal<HasMarginalPredictImpl, X, Fit<HasMarginalPredictImpl>>::value));
EXPECT_TRUE(bool(has_valid_predict_joint<HasJointPredictImpl, X, Fit<HasJointPredictImpl>>::value));
EXPECT_TRUE(bool(has_valid_predict_mean<HasAllPredictImpls, X, Fit<HasAllPredictImpls>>::value));
EXPECT_TRUE(bool(has_valid_predict_marginal<HasAllPredictImpls, X, Fit<HasAllPredictImpls>>::value));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some failure cases here would be good.

Copy link
Contributor

@seth-swiftnav seth-swiftnav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As someone who needed to look up wait a "trait" means in programming, I have limited usefulness in reviewing

I love the dozens of unit tests. As they not only test the code but also provide plenty of examples.

namespace albatross {

/*
* This little trick was borrowed from cereal, you an think of it as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: *can

@akleeman akleeman merged commit 1dc62d7 into swift-nav:crtp_refactor Mar 11, 2019
@akleeman akleeman deleted the crtp_traits branch March 11, 2019 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants