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

Be more generous in what types we allow in the FESystem(...) constructor #5090

Merged
merged 6 commits into from Sep 20, 2017

Conversation

bangerth
Copy link
Member

This is a follow-up to the new variadic constructor of FESystem introduced in #5026: We now allow the argument list to be composed of either (i) pairs of a pointer to finite element and multiplicity, or (ii) just finite element. This allows writing things such as

  FESystem (FE_Q(2)^dim, FE_Q(1));

where the second argument has no multiplicity.

This works by routing each argument to a function promote_to_pair() that just forwards the original pair if it really is a pair, or that creates a pair of that element and multiplicity one if it is not a pair.

The last commit extends this a bit by adding another function promote_to_pair() that just eats all other possible argument types but has a static_assert that explains in detail that you can't do that. For example, if you pass an integer as an argument, you'd get this sort of error message:

40: In file included from /home/fac/f/bangerth/p/deal.II/1/dealii/tests/fe/system_04.cc:25:0:
40: /home/fac/f/bangerth/p/deal.II/1/dealii/include/deal.II/fe/fe_system.h: In function ‘std::pair<std::unique_ptr<dealii::FiniteElement<<anonymous>, <anonymous> > >, unsigned int> dealii::{anonymous}::promote_to_fe_pair(const T&)’:
40: /home/fac/f/bangerth/p/deal.II/1/dealii/include/deal.II/fe/fe_system.h:1183:5: error: static assertion failed: The arguments to the FESystem constructor that takes a variable number of arguments all need to either be finite element objects, or pairs of a std::unique pointer to a finite element and an integer multiplicity (i.e., what you get when you write, for example, (FE_Q<dim>(2)^3'). But here, you are calling that constructor with an argument that is neither of these two options.
40:      static_assert (false,
40:      ^

There is a new test, system_04 that is a variation of system_03 that just omits the ^1 cases. The output is identical to the previous test.

@bangerth bangerth added the C++11 label Sep 14, 2017
@bangerth
Copy link
Member Author

See also #4249.

@masterleinad
Copy link
Member

Nice. That looks easier than expected.

I am bit surpised that this actually compiles for you. since the universal reference just matches everything. In particular, all the places where the old constructors are used are problematic and I get lots of compile errors using gcc-7.1.0.

Hence, I think that you have to go back to some std::enable_if approach.

@masterleinad
Copy link
Member

Regarding your change in FiniteElement I get errors like

 ../source/fe/fe.cc:159:24: error: no matching function for call to ‘make_pair<std::unique_ptr<dealii::FiniteElement<1, 1>, std::default_delete<dealii::                 FiniteElement<1, 1> > >, unsigned int>(std::remove_reference<std::unique_ptr<dealii::FiniteElement<1, 1>, std::default_delete<dealii::FiniteElement<1, 1> > > >::type,  const unsigned int&)’
    return std::make_pair<std::unique_ptr<FiniteElement<dim, spacedim>>,
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           unsigned int> (std::move(this->clone()), multiplicity);
           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 In file included from /opt/compiler/gcc-7.2.0/include/c++/7.2.0/bits/stl_algobase.h:64:0,
                  from /opt/compiler/gcc-7.2.0/include/c++/7.2.0/bits/specfun.h:45,
                  from /opt/compiler/gcc-7.2.0/include/c++/7.2.0/cmath:1914,
                  from ../include/deal.II/base/numbers.h:23,
                  from include/deal.II/base/config.h:335,
                  from ../include/deal.II/base/memory_consumption.h:20,
                  from ../source/fe/fe.cc:16:
 /opt/compiler/gcc-7.2.0/include/c++/7.2.0/bits/stl_pair.h:519:5: note: candidate: template<class _T1, class _T2> constexpr std::pair<typename std::                     __decay_and_strip<_Tp>::__type, typename std::__decay_and_strip<_T2>::__type> std::make_pair(_T1&&, _T2&&)
      make_pair(_T1&& __x, _T2&& __y)
      ^~~~~~~~~
 /opt/compiler/gcc-7.2.0/include/c++/7.2.0/bits/stl_pair.h:519:5: note:   template argument deduction/substitution failed:
 ../source/fe/fe.cc:159:24: note:   cannot convert ‘multiplicity’ (type ‘const unsigned int’) to type ‘unsigned int&&’
    return std::make_pair<std::unique_ptr<FiniteElement<dim, spacedim>>,
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           unsigned int> (std::move(this->clone()), multiplicity);
           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ../source/fe/fe.cc: In instantiation of ‘std::pair<std::unique_ptr<dealii::FiniteElement<<anonymous>, <anonymous> > >, unsigned int> dealii::                           FiniteElement<<anonymous>, <anonymous> >::operator^(unsigned int) const [with int dim = 1; int spacedim = 2]’:
 source/fe/fe.inst:17:17:   required from here

telling me that the second argument has to be a rvalue reference.

@masterleinad
Copy link
Member

Adding to the last comment, cppreference says that std::make_pair requires rvalue references from C++11 on.
The constructor of std::pair also allows both arguments to be const references, but this is also not applicable here since std::unique_ptr is not copy-constructible.

@bangerth
Copy link
Member Author

I see the issue with the compile error as well now. I apparently only verified with the tests that actually use the variadic constructor.

Will fix.

@bangerth
Copy link
Member Author

What a bizarre definition of std::make_pair, btw. But I have a workaround for that as well.

@bangerth
Copy link
Member Author

OK, this should work now. Can you take another look?

I had to work a bit with additional classes such as all_same, but I think that makes things a bit clearer. I also had to get rid of the function with the static_assert -- I just couldn't get that going, and it's now covered with the enable_if anyway. GCC error messages don't help either because they just show the error from the static_assert, but not how I got there nor the template arguments of the function in which this happens :-(

@@ -25,10 +25,26 @@

DEAL_II_NAMESPACE_OPEN

namespace
namespace internal
Copy link
Member

Choose a reason for hiding this comment

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

why don't you like the anonymous namespace here?

Copy link
Contributor

Choose a reason for hiding this comment

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

i think @drwells was recently fixing those, i.e. adding internal to anonymous namespaces. I forgot the reason, though 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

Anonymous namespaces in header files are generally a not so great idea.

An anonymous namespace is equivalent to a named namespace where the name is automatically created for each "translation unit" (i.e., .cc file that #includes the header). That means that a symbol in an anonymous namespace in a header file is distinct between two object files. Most of the time this has no practical impact other than the fact that the linker cannot, for example, unify the code for one function. But you'd get into trouble if you had, for example, a global variable in such a namespace, or maybe a static variable in a function in such a namespace: these variables are not in fact global, but duplicated in every .cc file that #includes the header file.

For such reasons, it's generally not a great idea to use anonymous namespaces in header files. Most of the time, nothing bad happens, but when something bad happens, it's a really bizarre situation that difficult to debug.

};


/*
* A generalization of `std::enable` that only works if
Copy link
Member

Choose a reason for hiding this comment

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

std::enable-> std::enable_if

*/
template <class Type, class... Types>
struct all_same_as
{
static constexpr bool value =
std::is_same<BoolStorage<std::is_same<Type, Types>::value..., true>,
BoolStorage<true, std::is_same<Type, Types>::value...>>::value;
internal::TemplateConstraints::all_true<std::is_same<Type, Types>::value...>::value;
};


Copy link
Member

Choose a reason for hiding this comment

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

This should be three blank lines, shouldn't it?

{
// helper struct for is_base_of_all and all_same_as
template <bool... Types> struct BoolStorage;
namespace TemplateConstraints
Copy link
Member

Choose a reason for hiding this comment

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

This would probably a good namespace name for everything in this file... Again, I don't quite see the need for a named namespace here.

template <class... FEPairs,
typename = typename enable_if_all<
(std::is_same<std::pair<std::unique_ptr<FiniteElement<dim, spacedim>>, unsigned int>,
typename std::decay<FEPairs>::type>::value
Copy link
Member

Choose a reason for hiding this comment

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

Don't we normally test using syntax like
x==1 instead of 1==x?

* true.
*/
template <bool... Values>
struct enable_if_all : std::enable_if<internal::TemplateConstraints::all_true<Values...>::value>
Copy link
Member

Choose a reason for hiding this comment

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

For this to be analogous to std::enable_if I would expect to be able to specify a type (default void) I can access via std::enable_if_all<...>::type if all of the statements are true. Can you add this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, that's sort of a problem. I had tried that, but to be analogous to std::enable_if, I would have to declare this as

template <bool... Values,
                 typename T = void>
struct enable_if_all : std::enable_if<..., T> {};

But that's not allowed: parameter packs must appear at the end of the template argument list. So my only choice would have been to move T to be the first argument, and then I can't give it a default value.

So, I didn't know what to do and went with the easy path which was to omit T altogether. I couldn't come up with a better solution. What do you think the best approach is, @masterleinad ?

@@ -1147,17 +1167,42 @@ namespace
return fe_system.second > 0;
});
}


template <int dim, int spacedim>
Copy link
Member

Choose a reason for hiding this comment

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

Also three blank lines.

@masterleinad
Copy link
Member

Apart from the things I commented on, I really like this.

Specifically, include the one that is necessary for std::enable_if.
While there, simplify some other classes a bit by introducing
an 'all_true' class. Also move classes into a named internal
namespace.
@bangerth
Copy link
Member Author

I think I addressed all of the issues other than the enable_if_all one.

@masterleinad
Copy link
Member

masterleinad commented Sep 18, 2017

What about about the following?

diff --git a/include/deal.II/base/template_constraints.h b/include/deal.II/base/template_constraints.h
index 1455c9f..31c20e8 100644
--- a/include/deal.II/base/template_constraints.h
+++ b/include/deal.II/base/template_constraints.h
@@ -77,14 +77,28 @@ struct all_same_as
 
 
 
-/*
+/**
  * A generalization of `std::enable_if` that only works if
  * <i>all</i> of the given boolean template parameters are
  * true.
+ * In this case, a type <code>T</code> can be stored and accessed
+ * using the following syntax:
+ * @code
+ *   using Type = std::enable_if_all<Values...>::template type<T>;
+ * @endcode
+ * By default, <code>T=default</code> just as for the regular
+ * <code>std::enable_if</code> and the above example simplifies to:
+ * @code
+ *   using Type = std::enable_if_all<Values...>::template type<>;
+ * @endcode
  */
 template <bool... Values>
-struct enable_if_all : std::enable_if<internal::TemplateConstraints::all_true<Values...>::value>
-{};
+struct enable_if_all
+{
+  template<typename T = void>
+  using type =
+    typename std::enable_if<internal::TemplateConstraints::all_true<Values...>::value, T>::type;
+};
 
 
 
diff --git a/include/deal.II/fe/fe_system.h b/include/deal.II/fe/fe_system.h
index f921833..221b5c8 100644
--- a/include/deal.II/fe/fe_system.h
+++ b/include/deal.II/fe/fe_system.h
@@ -469,7 +469,7 @@ public:
                std::is_base_of<FiniteElement<dim, spacedim>,
                                typename std::decay<FEPairs>::type>::value)
               ...
-              >::type
+              >::template type<>
             >
   FESystem (FEPairs &&... fe_pairs);

edit: default type parameter

@bangerth
Copy link
Member Author

Hm, I tried this, but I can't seem to make it work. Does the patch you propose above actually work for you, including all of the existing and new tests?

@masterleinad
Copy link
Member

Using

 template <class... FEPairs,
             typename = typename enable_if_all<
               (std::is_same<typename std::decay<FEPairs>::type,
                             std::pair<std::unique_ptr<FiniteElement<dim, spacedim>>, unsigned int>>::value
                ||
                std::is_base_of<FiniteElement<dim, spacedim>,
                                typename std::decay<FEPairs>::type>::value)
               ...
               >::template type<>
             >

in fe/fe_system.h works for me.

@bangerth
Copy link
Member Author

I tried this again but it doesn't seem to work with GCC 4.8. This may well be a compiler bug in C++11 support. The errors are like this:

/home/fac/f/bangerth/p/deal.II/1/dealii/include/deal.II/base/template_constraints.h: In substitution of ‘template<bool ...Values> template<class T> using type = typename std::enable_if<dealii::internal::TemplateConstraints::all_true<Values>::value, T>::type [with T = void; bool ...Values = {false, false}]’:
/home/fac/f/bangerth/p/deal.II/1/dealii/include/deal.II/fe/fe_system.h:465:13:   required from ‘dealii::FE_Enriched<dim, spacedim>::FE_Enriched(const std::vector<const dealii::FiniteElement<dim, spacedim>*>&, const std::vector<unsigned int>&, const std::vector<std::vector<std::function<const dealii::Function<spacedim>*(const typename dealii::Triangulation<dim, spacedim>::cell_iterator&)> > >&) [with int dim = 1; int spacedim = 1; typename dealii::Triangulation<dim, spacedim>::cell_iterator = dealii::TriaIterator<dealii::CellAccessor<1, 1> >]’
source/fe/fe_enriched.inst:11:17:   required from here
/home/fac/f/bangerth/p/deal.II/1/dealii/include/deal.II/base/template_constraints.h:100:107: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
   using type = typename std::enable_if<internal::TemplateConstraints::all_true<Values...>::value, T>::type;
                                                                                                           ^

In other words, SFINAE doesn't seem to actually work here.

How do you want to proceed? We don't actually need the nested type, so the current void type would work just fine.

@masterleinad
Copy link
Member

Let's just stick with the current version. We can alyways extend enable_if_all later if we want to use it with a non-void type.

@masterleinad
Copy link
Member

/run-tests

@masterleinad
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants