diff --git a/mlir/examples/standalone/CMakeLists.txt b/mlir/examples/standalone/CMakeLists.txt
--- a/mlir/examples/standalone/CMakeLists.txt
+++ b/mlir/examples/standalone/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR ON)
 
-set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to conform to")
+set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to")
 
 find_package(MLIR REQUIRED CONFIG)
 
diff --git a/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h b/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h
--- a/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h
+++ b/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h
@@ -52,10 +52,8 @@
   template <typename... DialectTs>
   void allowDialect() {
     // The following expands a call to allowDialectImpl for each dialect
-    // in 'DialectTs'. This magic is necessary due to a limitation in the places
-    // that a parameter pack can be expanded in c++11.
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{0, (allowDialectImpl<DialectTs>(), 0)...};
+    // in 'DialectTs'.
+    (allowDialectImpl<DialectTs>(), ...);
   }
 
   /// Deny the given dialects.
@@ -63,8 +61,7 @@
   /// This function adds one or multiple DENY entries.
   template <typename... DialectTs>
   void denyDialect() {
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{0, (denyDialectImpl<DialectTs>(), 0)...};
+    (denyDialectImpl<DialectTs>(), ...);
   }
 
   /// Allow the given dialect.
@@ -82,8 +79,7 @@
   /// This function adds one or multiple ALLOW entries.
   template <typename... OpTys>
   void allowOperation() {
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{0, (allowOperationImpl<OpTys>(), 0)...};
+    (allowOperationImpl<OpTys>(), ...);
   }
 
   /// Deny the given ops.
@@ -91,8 +87,7 @@
   /// This function adds one or multiple DENY entries.
   template <typename... OpTys>
   void denyOperation() {
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{0, (denyOperationImpl<OpTys>(), 0)...};
+    (denyOperationImpl<OpTys>(), ...);
   }
 
   /// Allow the given op.
diff --git a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
--- a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
+++ b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
@@ -334,13 +334,10 @@
       /// dialect. Checks that they implement the required interfaces.
       template <typename... OpTys>
       void addOperationsChecked() {
-        (void)std::initializer_list<int>{(addOperationIfNotRegistered<OpTys>(),
-                                          0)...};
+        (addOperationIfNotRegistered<OpTys>(),...);
 
         #ifndef NDEBUG
-        (void)std::initializer_list<int>{
-          (detail::checkImplementsTransformInterface<OpTys>(getContext()),
-           0)...};
+        (detail::checkImplementsTransformInterface<OpTys>(getContext()),...);
         #endif // NDEBUG
       }
 
diff --git a/mlir/include/mlir/ExecutionEngine/ExecutionEngine.h b/mlir/include/mlir/ExecutionEngine/ExecutionEngine.h
--- a/mlir/include/mlir/ExecutionEngine/ExecutionEngine.h
+++ b/mlir/include/mlir/ExecutionEngine/ExecutionEngine.h
@@ -172,9 +172,7 @@
     llvm::SmallVector<void *> argsArray;
     // Pack every arguments in an array of pointers. Delegate the packing to a
     // trait so that it can be overridden per argument type.
-    // TODO: replace with a fold expression when migrating to C++17.
-    int dummy[] = {0, ((void)Argument<Args>::pack(argsArray, args), 0)...};
-    (void)dummy;
+    (Argument<Args>::pack(argsArray, args), ...);
     return invokePacked(adapterName, argsArray);
   }
 
diff --git a/mlir/include/mlir/IR/BuiltinAttributes.h b/mlir/include/mlir/IR/BuiltinAttributes.h
--- a/mlir/include/mlir/IR/BuiltinAttributes.h
+++ b/mlir/include/mlir/IR/BuiltinAttributes.h
@@ -84,7 +84,6 @@
   /// Type trait used to check if the given type T is a potentially valid C++
   /// floating point type that can be used to access the underlying element
   /// types of a DenseElementsAttr.
-  // TODO: Use std::disjunction when C++17 is supported.
   template <typename T>
   struct is_valid_cpp_fp_type {
     /// The type is a valid floating point type if it is a builtin floating
diff --git a/mlir/include/mlir/IR/BuiltinTypes.td b/mlir/include/mlir/IR/BuiltinTypes.td
--- a/mlir/include/mlir/IR/BuiltinTypes.td
+++ b/mlir/include/mlir/IR/BuiltinTypes.td
@@ -262,7 +262,7 @@
 
     /// Integer representation maximal bitwidth.
     /// Note: This is aligned with the maximum width of llvm::IntegerType.
-    static constexpr unsigned kMaxWidth = (1 << 24) - 1;
+    static constexpr inline unsigned kMaxWidth = (1 << 24) - 1;
   }];
 }
 
diff --git a/mlir/include/mlir/IR/Dialect.h b/mlir/include/mlir/IR/Dialect.h
--- a/mlir/include/mlir/IR/Dialect.h
+++ b/mlir/include/mlir/IR/Dialect.h
@@ -186,8 +186,7 @@
   /// Register a set of dialect interfaces with this dialect instance.
   template <typename... Args>
   void addInterfaces() {
-    (void)std::initializer_list<int>{
-        0, (addInterface(std::make_unique<Args>(this)), 0)...};
+    (addInterface(std::make_unique<Args>(this)), ...);
   }
   template <typename InterfaceT, typename... Args>
   InterfaceT &addInterface(Args &&...args) {
@@ -210,6 +209,10 @@
   ///
   template <typename... Args>
   void addOperations() {
+    // This initializer_list argument pack expansion is essentially equal to
+    // using a fold expression with a comma operator. Clang however, refuses
+    // to compile a fold expression with a depth of more than 256 by default.
+    // There seem to be no such limitations for initializer_list.
     (void)std::initializer_list<int>{
         0, (RegisteredOperationName::insert<Args>(*this), 0)...};
   }
@@ -217,7 +220,7 @@
   /// Register a set of type classes with this dialect.
   template <typename... Args>
   void addTypes() {
-    (void)std::initializer_list<int>{0, (addType<Args>(), 0)...};
+    (addType<Args>(), ...);
   }
 
   /// Register a type instance with this dialect.
@@ -228,7 +231,7 @@
   /// Register a set of attribute classes with this dialect.
   template <typename... Args>
   void addAttributes() {
-    (void)std::initializer_list<int>{0, (addAttribute<Args>(), 0)...};
+    (addAttribute<Args>(), ...);
   }
 
   /// Register an attribute instance with this dialect.
diff --git a/mlir/include/mlir/IR/DialectRegistry.h b/mlir/include/mlir/IR/DialectRegistry.h
--- a/mlir/include/mlir/IR/DialectRegistry.h
+++ b/mlir/include/mlir/IR/DialectRegistry.h
@@ -175,8 +175,7 @@
   /// Add the given extensions to the registry.
   template <typename... ExtensionsT>
   void addExtensions() {
-    (void)std::initializer_list<int>{
-        (addExtension(std::make_unique<ExtensionsT>()), 0)...};
+    (addExtension(std::make_unique<ExtensionsT>()), ...);
   }
 
   /// Add an extension function that requires the given dialects.
diff --git a/mlir/include/mlir/IR/Matchers.h b/mlir/include/mlir/IR/Matchers.h
--- a/mlir/include/mlir/IR/Matchers.h
+++ b/mlir/include/mlir/IR/Matchers.h
@@ -224,10 +224,9 @@
 template <typename TupleT, class CallbackT, std::size_t... Is>
 constexpr void enumerateImpl(TupleT &&tuple, CallbackT &&callback,
                              std::index_sequence<Is...>) {
-  (void)std::initializer_list<int>{
-      0,
-      (callback(std::integral_constant<std::size_t, Is>{}, std::get<Is>(tuple)),
-       0)...};
+
+  (callback(std::integral_constant<std::size_t, Is>{}, std::get<Is>(tuple)),
+   ...);
 }
 
 template <typename... Tys, typename CallbackT>
diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h
--- a/mlir/include/mlir/IR/OpDefinition.h
+++ b/mlir/include/mlir/IR/OpDefinition.h
@@ -1492,12 +1492,10 @@
 template <typename T>
 using detect_has_fold_trait = llvm::is_detected<has_fold_trait, T>;
 /// Trait to check if T provides any `foldTrait` method.
-/// NOTE: This should use std::disjunction when C++17 is available.
 template <typename T>
 using detect_has_any_fold_trait =
-    std::conditional_t<bool(detect_has_fold_trait<T>::value),
-                       detect_has_fold_trait<T>,
-                       detect_has_single_result_fold_trait<T>>;
+    std::disjunction<detect_has_fold_trait<T>,
+                     detect_has_single_result_fold_trait<T>>;
 
 /// Returns the result of folding a trait that implements a `foldTrait` function
 /// that is specialized for operations that have a single result.
@@ -1543,10 +1541,7 @@
 template <typename... Ts>
 static LogicalResult foldTraits(Operation *op, ArrayRef<Attribute> operands,
                                 SmallVectorImpl<OpFoldResult> &results) {
-  bool anyFolded = false;
-  (void)std::initializer_list<int>{
-      (anyFolded |= succeeded(foldTrait<Ts>(op, operands, results)), 0)...};
-  return success(anyFolded);
+  return success((succeeded(foldTrait<Ts>(op, operands, results)) | ...));
 }
 
 //===----------------------------------------------------------------------===//
@@ -1581,10 +1576,7 @@
 /// Given a set of traits, return the result of verifying the given operation.
 template <typename... Ts>
 LogicalResult verifyTraits(Operation *op) {
-  LogicalResult result = success();
-  (void)std::initializer_list<int>{
-      (result = succeeded(result) ? verifyTrait<Ts>(op) : failure(), 0)...};
-  return result;
+  return success((succeeded(verifyTrait<Ts>(op)) && ...));
 }
 
 /// Verify the given trait if it provides a region verifier.
@@ -1604,12 +1596,7 @@
 /// given operation.
 template <typename... Ts>
 LogicalResult verifyRegionTraits(Operation *op) {
-  (void)op;
-  LogicalResult result = success();
-  (void)std::initializer_list<int>{
-      (result = succeeded(result) ? verifyRegionTrait<Ts>(op) : failure(),
-       0)...};
-  return result;
+  return success((succeeded(verifyRegionTrait<Ts>(op)) && ...));
 }
 } // namespace op_definition_impl
 
@@ -1695,7 +1682,7 @@
       llvm::report_fatal_error(
           "Attempting to attach an interface to an unregistered operation " +
           ConcreteType::getOperationName() + ".");
-    (void)std::initializer_list<int>{(checkInterfaceTarget<Models>(), 0)...};
+    (checkInterfaceTarget<Models>(), ...);
     info->attachInterface<Models...>();
   }
 
diff --git a/mlir/include/mlir/IR/PatternMatch.h b/mlir/include/mlir/IR/PatternMatch.h
--- a/mlir/include/mlir/IR/PatternMatch.h
+++ b/mlir/include/mlir/IR/PatternMatch.h
@@ -1079,15 +1079,10 @@
   auto errorFn = [&](const Twine &msg) {
     return rewriter.notifyMatchFailure(rewriter.getUnknownLoc(), msg);
   };
-  LogicalResult result = success();
-  (void)std::initializer_list<int>{
-      (result =
-           succeeded(result)
-               ? ProcessPDLValue<typename FnTraitsT::template arg_t<I + 1>>::
-                     verifyAsArg(errorFn, values[I], I)
-               : failure(),
-       0)...};
-  return result;
+  return success(
+      (succeeded(ProcessPDLValue<typename FnTraitsT::template arg_t<I + 1>>::
+                     verifyAsArg(errorFn, values[I], I)) &&
+       ...));
 }
 
 /// Assert that the given PDLValues match the constraints defined by the
@@ -1102,10 +1097,10 @@
   auto errorFn = [&](const Twine &msg) -> LogicalResult {
     llvm::report_fatal_error(msg);
   };
-  (void)std::initializer_list<int>{
-      (assert(succeeded(ProcessPDLValue<typename FnTraitsT::template arg_t<
-                            I + 1>>::verifyAsArg(errorFn, values[I], I))),
-       0)...};
+  (assert(succeeded(
+       ProcessPDLValue<typename FnTraitsT::template arg_t<I + 1>>::verifyAsArg(
+           errorFn, values[I], I))),
+   ...);
 #endif
 }
 
@@ -1134,10 +1129,7 @@
 static void processResults(PatternRewriter &rewriter, PDLResultList &results,
                            std::tuple<Ts...> &&tuple) {
   auto applyFn = [&](auto &&...args) {
-    // TODO: Use proper fold expressions when we have C++17. For now we use a
-    // bogus std::initializer_list to work around C++14 limitations.
-    (void)std::initializer_list<int>{
-        (processResults(rewriter, results, std::move(args)), 0)...};
+    (processResults(rewriter, results, std::move(args)), ...);
   };
   llvm::apply_tuple(applyFn, std::move(tuple));
 }
@@ -1412,14 +1404,10 @@
             typename = std::enable_if_t<sizeof...(Ts) != 0>>
   RewritePatternSet &add(ConstructorArg &&arg, ConstructorArgs &&...args) {
     // The following expands a call to emplace_back for each of the pattern
-    // types 'Ts'. This magic is necessary due to a limitation in the places
-    // that a parameter pack can be expanded in c++11.
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{
-        0, (addImpl<Ts>(/*debugLabels=*/llvm::None,
-                        std::forward<ConstructorArg>(arg),
-                        std::forward<ConstructorArgs>(args)...),
-            0)...};
+    // types 'Ts'.
+    (addImpl<Ts>(/*debugLabels=*/llvm::None, std::forward<ConstructorArg>(arg),
+                 std::forward<ConstructorArgs>(args)...),
+     ...);
     return *this;
   }
   /// An overload of the above `add` method that allows for attaching a set
@@ -1433,11 +1421,8 @@
                                   ConstructorArg &&arg,
                                   ConstructorArgs &&...args) {
     // The following expands a call to emplace_back for each of the pattern
-    // types 'Ts'. This magic is necessary due to a limitation in the places
-    // that a parameter pack can be expanded in c++11.
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{
-        0, (addImpl<Ts>(debugLabels, arg, args...), 0)...};
+    // types 'Ts'.
+    (addImpl<Ts>(debugLabels, arg, args...), ...);
     return *this;
   }
 
@@ -1445,7 +1430,7 @@
   /// `this` for chaining insertions.
   template <typename... Ts>
   RewritePatternSet &add() {
-    (void)std::initializer_list<int>{0, (addImpl<Ts>(), 0)...};
+    (addImpl<Ts>(), ...);
     return *this;
   }
 
@@ -1498,11 +1483,8 @@
             typename = std::enable_if_t<sizeof...(Ts) != 0>>
   RewritePatternSet &insert(ConstructorArg &&arg, ConstructorArgs &&...args) {
     // The following expands a call to emplace_back for each of the pattern
-    // types 'Ts'. This magic is necessary due to a limitation in the places
-    // that a parameter pack can be expanded in c++11.
-    // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-    (void)std::initializer_list<int>{
-        0, (addImpl<Ts>(/*debugLabels=*/llvm::None, arg, args...), 0)...};
+    // types 'Ts'.
+    (addImpl<Ts>(/*debugLabels=*/llvm::None, arg, args...), ...);
     return *this;
   }
 
@@ -1510,7 +1492,7 @@
   /// `this` for chaining insertions.
   template <typename... Ts>
   RewritePatternSet &insert() {
-    (void)std::initializer_list<int>{0, (addImpl<Ts>(), 0)...};
+    (addImpl<Ts>(), ...);
     return *this;
   }
 
diff --git a/mlir/include/mlir/IR/StorageUniquerSupport.h b/mlir/include/mlir/IR/StorageUniquerSupport.h
--- a/mlir/include/mlir/IR/StorageUniquerSupport.h
+++ b/mlir/include/mlir/IR/StorageUniquerSupport.h
@@ -138,8 +138,8 @@
     if (!abstract)
       llvm::report_fatal_error("Registering an interface for an attribute/type "
                                "that is not itself registered.");
-    (void)std::initializer_list<int>{
-        (checkInterfaceTarget<IfaceModels>(), 0)...};
+
+    (checkInterfaceTarget<IfaceModels>(), ...);
     abstract->interfaceMap.template insert<IfaceModels...>();
   }
 
diff --git a/mlir/include/mlir/Support/InterfaceSupport.h b/mlir/include/mlir/Support/InterfaceSupport.h
--- a/mlir/include/mlir/Support/InterfaceSupport.h
+++ b/mlir/include/mlir/Support/InterfaceSupport.h
@@ -191,16 +191,14 @@
   /// do not represent interfaces are not added to the interface map.
   template <typename... Types>
   static InterfaceMap get() {
-    // TODO: Use constexpr if here in C++17.
     constexpr size_t numInterfaces = num_interface_types_t<Types...>::value;
-    if (numInterfaces == 0)
+    if constexpr (numInterfaces == 0)
       return InterfaceMap();
 
     std::array<std::pair<TypeID, void *>, numInterfaces> elements;
     std::pair<TypeID, void *> *elementIt = elements.data();
     (void)elementIt;
-    (void)std::initializer_list<int>{
-        0, (addModelAndUpdateIterator<Types>(elementIt), 0)...};
+    (addModelAndUpdateIterator<Types>(elementIt), ...);
     return InterfaceMap(elements);
   }
 
diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
--- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
+++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
@@ -178,15 +178,12 @@
   }
 
   /// The minimum alignment to use with aligned_alloc (has to be a power of 2).
-  static constexpr uint64_t kMinAlignedAllocAlignment = 16UL;
+  static inline constexpr uint64_t kMinAlignedAllocAlignment = 16UL;
 
   /// Default layout to use in absence of the corresponding analysis.
   DataLayout defaultLayout;
 };
 
-// Out of line definition, required till C++17.
-constexpr uint64_t AlignedAllocOpLowering::kMinAlignedAllocAlignment;
-
 struct AllocaOpLowering : public AllocLikeOpLLVMLowering {
   AllocaOpLowering(LLVMTypeConverter &converter)
       : AllocLikeOpLLVMLowering(memref::AllocaOp::getOperationName(),
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgDialect.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgDialect.cpp
--- a/mlir/lib/Dialect/Linalg/IR/LinalgDialect.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgDialect.cpp
@@ -96,8 +96,7 @@
 template <typename... OpTypes>
 void addNamedOpBuilders(
     llvm::StringMap<LinalgDialect::RegionBuilderFunType> &map) {
-  (void)std::initializer_list<int>{0,
-                                   (addNamedOpBuilderImpl<OpTypes>(map), 0)...};
+  (addNamedOpBuilderImpl<OpTypes>(map), ...);
 }
 
 void mlir::linalg::LinalgDialect::initialize() {
diff --git a/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp b/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
--- a/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
@@ -137,8 +137,7 @@
 template <typename... Ops>
 struct LinalgOpInterfaceHelper {
   static void registerOpInterface(MLIRContext *ctx) {
-    (void)std::initializer_list<int>{
-        0, (Ops::template attachInterface<LinalgOpInterface<Ops>>(*ctx), 0)...};
+    (Ops::template attachInterface<LinalgOpInterface<Ops>>(*ctx), ...);
   }
 };
 } // namespace
diff --git a/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp b/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
--- a/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
@@ -263,8 +263,7 @@
 /// Variadic helper function.
 template <typename... OpTypes>
 static void registerAll(MLIRContext *ctx) {
-  // FIXME: In c++17 this can be simplified by using 'fold expressions'.
-  (void)std::initializer_list<int>{0, (registerOne<OpTypes>(ctx), 0)...};
+  (registerOne<OpTypes>(ctx), ...);
 }
 
 #define GET_OP_LIST
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaLayerwiseConstantFoldPass.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaLayerwiseConstantFoldPass.cpp
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaLayerwiseConstantFoldPass.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaLayerwiseConstantFoldPass.cpp
@@ -23,8 +23,7 @@
 
 template <typename... Args>
 void addOpsCanonicalizations(MLIRContext *ctx, RewritePatternSet &patterns) {
-  (void)std::initializer_list<int>{
-      0, (Args::getCanonicalizationPatterns(patterns, ctx), 0)...};
+  (Args::getCanonicalizationPatterns(patterns, ctx), ...);
 }
 
 void populateTosaOpsCanonicalizationPatterns(MLIRContext *ctx,
diff --git a/mlir/lib/IR/BuiltinTypes.cpp b/mlir/lib/IR/BuiltinTypes.cpp
--- a/mlir/lib/IR/BuiltinTypes.cpp
+++ b/mlir/lib/IR/BuiltinTypes.cpp
@@ -60,9 +60,6 @@
 // Integer Type
 //===----------------------------------------------------------------------===//
 
-// static constexpr must have a definition (until in C++17 and inline variable).
-constexpr unsigned IntegerType::kMaxWidth;
-
 /// Verify the construction of an integer type.
 LogicalResult IntegerType::verify(function_ref<InFlightDiagnostic()> emitError,
                                   unsigned width,